
الگوی طراحـــــــــی Chain Of Responsibility
فرض کنید که ما یک سرویس ثبت سفارش داریم. تنها کاربری میتواند سفارش ثبت کند که احراز هویت شده باشد.
در ابتدا که شما این سایت رو راه اندازی کردین، فیلدهایی که چک میکنین برای احراز هویت کاربر، کم است. مثلا سه فیلد را چک میکنید. اما به مرور زمان روز به روز آیتم های شما برای چک کردن کاربر بیشتر و بیشتر میشود.
اضافه کردن فیلدهای مختلف بدون تدبیر قبلی باعث کشف شدن و پیچیدگی کد اهراز هویت میشود. همچنین شما نیاز داریم که در جاهای دیگر پروژتون فقط از بخشی از فیچرهای احراز هویتتان استفاده کنید. این باعث redundancy و code duplicate میشود. همچنین چنین پروژه ای هزینه توسعه اش بسیار بالا میرود. (از نظر مالی و زمانی).
مانند دیگر پترن های دسته behavioral هر بخش بصورت مستقل تبدیل به یک object شده و از آن استفاده میشود.
مثلا در مثال ما، هر چک برای Auth باید یک کلاس شود فقط با یک متد که ان متد وظیفه هندل کردن بخش خاصی از authorization را برعهده دارد.
هر handler میتونه تصمیم بگیره که آیا به بخش های بعدی میتونه Data رو پاس بده یا همان جا فرآیند stop شده و پیامی درخور آن بخش به client code ارسال شود.
این الگوی طراحی موقعی که eventها در پشته ای (stack) عناصر در یک رابط گرافیکی قرار دارند، بسیار رایج است.
بهترین مثالی که میتونیم براتون بزنیم در دنیای واقعی customer serviceها هستند.
شما وقتی میخواهید پیگیر خدمات اپراتورتون بشین (برای مثال رایتل)، ابتدا با یک اپراتور ماشینی گویا در تماس هستین. بین 1 تا 9 انتخاب میکنین. وقتی شماره 9 رو میگیرین به اپراتور واقعی که یک انسان است دسترسی پیدا میکنین .
نکته:
’’ هریک از مراحل امکان داره توسط کاربر یا سامانه قطع بشه (مثلا وسط صحبت اپراتور خودکار شما گوشی رو قطع کنین 😊). ‘‘
زمانی از الگوی زنجیره مسئولیت استفاده کنید که انتظار می رود برنامه شما انواع مختلفی از درخواست ها را به طرق مختلف پردازش کند، اما نوع دقیق درخواست ها و توالی آنها از قبل ناشناخته است.
این الگو به شما امکان می دهد چندین کنترل کننده را به یک زنجیره متصل کنید و پس از دریافت درخواست، از هر کنترل کننده بپرسید که آیا می تواند آن را پردازش کند یا خیر. به این ترتیب همه گردانندگان فرصتی برای پردازش درخواست دارند.
از الگوی زمانی استفاده کنید که اجرای چندین کنترل کننده در یک ترتیب خاص ضروری است.
از آنجایی که میتوانید کنترلکنندههای زنجیره را به هر ترتیبی پیوند دهید، تمام درخواستها دقیقاً همانطور که برنامهریزی کردهاید از طریق زنجیره دریافت میشوند.
از الگوی CoR زمانی استفاده کنید که مجموعه کنترل کننده ها و ترتیب آنها در زمان اجرا تغییر کند. اگر setterهایی را برای یک فیلد مرجع در کلاسهای کنترلکننده ارائه کنید، میتوانید کنترلکنندهها را به صورت پویا درج کنید، حذف کنید یا مرتب کنید.
به زبان سادهتر، الگوی طراحی Chain of Responsibility یک روش برای پردازش درخواستها به ترتیب مشخص است. در این الگو، شما یک سری از اشیاء کنترلکننده (Handler) دارید که هر کدام میتوانند درخواستها را پردازش کنند یا آنها را به کنترلکننده بعدی انتقال دهند. وظیفه این زنجیره از کنترلکنندهها، انجام یک مرحله از پردازش درخواست است و در صورتی که یک کنترلکننده خاص نتواند درخواست را پردازش کند، آن را به کنترلکننده بعدی در زنجیره منتقل میکند.
اجزاء الگو به شرح زیر هستند:
1. درخواست (Request): این میتواند یک شیء ساده با اطلاعات مربوط به درخواست باشد که باید توسط کنترلکنندهها پردازش شود.
2. کنترلکننده (Handler): این یک رابط یا کلاس انتزاعی است که متد handleRequest را دارد. کنترلکنندهها مسئولیت پردازش درخواستها را دارند.
3. کنترلکنندههای کنکاشی (Concrete Handlers): اینها کلاسهایی هستند که از کنترلکننده ارثبری میکنند. هر کنترلکننده کنکاشی متد handleRequest را پیادهسازی میکند و تصمیم میگیرد که آیا درخواست را پردازش کند یا آن را به کنترلکننده بعدی منتقل کند.
الگوی طراحی Chain of Responsibility (زنجیره مسئولیت) یک روش برای پردازش درخواستها به ترتیب مشخص است. در این الگو، شما یک سری از اشیاء کنترلکننده (Handler) دارید که هر کدام میتوانند درخواستها را پردازش کنند یا آنها را به کنترلکننده بعدی انتقال دهند. وظیفه این زنجیره از کنترلکنندهها، انجام یک مرحله از پردازش درخواست است و در صورتی که یک کنترلکننده خاص نتواند درخواست را پردازش کند، آن را به کنترلکننده بعدی در زنجیره منتقل میکند.
مثال واقعیتری را در نظر بگیرید: سیستمیلاگگیری که با استفاده از الگوی Chain of Responsibility پیادهسازی شده است. در این سناریو، چندین کنترلکننده مسئول برای ثبت انواع مختلف پیامها وجود دارد. هر کنترلکننده تصمیم میگیرد که آیا میتواند پیام را پردازش کند یا آن را به کنترلکننده بعدی در زنجیره منتقل کند.
1. کلاس درخواست (LogMessage):
1public class LogMessage
2{
3 public string Message { get; }
4
5 public LogMessage(string message)
6 {
7 Message = message;
8 }
9}
2. تعریف رابط کنترلکننده (ILogHandler):
1public interface ILogHandler
2{
3 void SetNext(ILogHandler handler);
4 void HandleLog(LogMessage logMessage);
5}
3. پیادهسازی کنترلکنندههای کنکاشی (Concrete Handlers):
1public class ConsoleLogHandler : ILogHandler
2{
3 private ILogHandler _nextHandler;
4
5 public void SetNext(ILogHandler handler)
6 {
7 _nextHandler = handler;
8 }
9
10 public void HandleLog(LogMessage logMessage)
11 {
12 if (logMessage.Message.StartsWith("CONSOLE:"))
13 {
14 Console.WriteLine($"Console Log: {logMessage.Message.Substring(8)}");
15 }
16 else
17 {
18 _nextHandler?.HandleLog(logMessage);
19 }
20 }
21}
22
23public class FileLogHandler : ILogHandler
24{
25 private ILogHandler _nextHandler;
26
27 public void SetNext(ILogHandler handler)
28 {
29 _nextHandler = handler;
30 }
31
32 public void HandleLog(LogMessage logMessage)
33 {
34 if (logMessage.Message.StartsWith("FILE:"))
35 {
36 // شبیهسازی نوشتن پیام در یک فایل لاگ
37 Console.WriteLine($"File Log: {logMessage.Message.Substring(5)}");
38 }
39 else
40 {
41 _nextHandler?.HandleLog(logMessage);
42 }
43 }
44}
45
46public class EmailLogHandler : ILogHandler
47{
48 private ILogHandler _nextHandler;
49
50 public void SetNext(ILogHandler handler)
51 {
52 _nextHandler = handler;
53 }
54
55 public void HandleLog(LogMessage logMessage)
56 {
57 if (logMessage.Message.StartsWith("EMAIL:"))
58 {
59 // شبیهسازی ارسال ایمیل با پیام لاگ
60 Console.WriteLine($"Sending Email Log: {logMessage.Message.Substring(6)}");
61 }
62 else
63 {
64 _nextHandler?.HandleLog(logMessage);
65 }
66 }
67}
4. تنظیم زنجیره:
1// ایجاد نمونههای کنترلکنندههای لاگ
2ILogHandler consoleHandler = new ConsoleLogHandler();
3ILogHandler fileHandler = new FileLogHandler();
4ILogHandler emailHandler = new EmailLogHandler();
5
6// تنظیم زنجیره
7consoleHandler.SetNext(fileHandler);
8fileHandler.SetNext(emailHandler);
5. پردازش پیامهای لاگ:
1LogMessage log1 = new LogMessage("CONSOLE: that's a console log message");
2LogMessage log2 = new LogMessage("FILE: that's a save log message");
3LogMessage log3 = new LogMessage("EMAIL: that's a send log message");
4
5// شروع پردازش پیامهای لاگ
6consoleHandler.HandleLog(log1);
7consoleHandler.HandleLog(log2);
8consoleHandler.HandleLog(log3);
در این مثال، سه نوع کنترلکننده برای ثبت لاگ وجود دارد: `ConsoleLogHandler`، `FileLogHandler` و `EmailLogHandler`. هر کنترلکننده با بررسی پیشوند پیام تصمیم میگیرد که آیا میتواند لاگ را پردازش کند یا آن را به کنترلکننده بعدی در زنجیره منتقل کند.
اگر پیام لاگ با "CONSOLE:" شروع شود، توسط `ConsoleLogHandler` پردازش خواهد شد؛ اگر با "FILE:" شروع شود، توسط `FileLogHandler` پردازش و به عنوان لاگ فایل ذخیره خواهد شد؛ و اگر با "EMAIL:" شروع شود، توسط `EmailLogHandler` پردازش و به عنوان یک لاگ مهم به صورت ایمیل ارسال خواهد شد. اگر هیچکدام از کنترلکنندهها نتوانند پیام لاگ را پردازش کنند، پیام لاگ بدون پردازش به پایان میرسد.
خروجی اجرای این کد به این صورت خواهد بود:
Console Log: that's a console log message
File Log: that's a save log message
Sending Email Log: that's a send log message
همانطور که میبینید، پیامهای لاگ به ترتیب درخواست شده توسط کنترلکنندهها پردازش میشوند و هر کنترلکننده وظیفهای که میتواند انجام دهد را انجام میدهد یا درخواست را به کنترلکننده بعدی منتقل میکند تا تا زمانی که پیام لاگ توسط یکی از کنترلکنندهها پردازش شود.