حوزه: Object
هدف: Behavioral
نقش الگو
این الگو یک راه حل برای حالتی است که، در آن درخواستهایی از کاربر به برنامه میرسد و باید با توجه به شرایطی به این درخواستها پاسخ داده شود و در موارد زیر کاربرد دارد.
- زمانی که برای پاسخ دادن به درخواست کاربر بیش از یک Handler (که میتواند یک برنامه، یک thread،شی ای از یک کلاس، یک تابع یا بخشی از برنامه باشد. handlerها به درخواستهای کاربر پاسخ میدهند و البته handlerها بخشی از برنامه هستند) داریم.
- زمانی که به دلایلی و بر اساس نوع درخواست کاربر handlerهای مختلفی برای پاسخ گویی به درخواستها داریم.
- زمانی که handlerهای در دسترس بطور پویا تغییر میکنند (مثلا یک handler موقتا مشغول باشد یا به هر دلیلی در دسترس نباشد و handler دیگری باید به درخواست کاربر پاسخ دهد.)
- زمانی که میخواهیم برنامه برای اختصاص دادن handlerها به درخواستهای کاربر یا کاربران انعطاف پذیر باشد.
حال که با موارد استفادهی Chain of Responsibility آشنا شدیم، به طرز کار و پیاده سازی آن میپردازیم، الگوی Chain of Responsibility با لیستی از اشیای handler کار میکند که محدودیتهایی برای پاسخ گویی به درخواستهای کاربر یا کاربران بر اساس نوع درخواستها بر آنها اعمال شده است، اگر یک handler نتواند به یک درخواست پاسخ دهد درخواست را برای handler بعدی در این لیست (زنجیره) میفرستد و به همین ترتیب برنامه کار خواهد کرد و در انتهای زنجیره میتوان یک حالت استثنا یا یک پیشفرض قرار داد تا در صورتی که هیچکدام از handlerها به درخواست پاسخ ندادند آن حالت پیشفرض اتفاق بیافتد و یا به کاربر خطایی نمایش داده شود.
Chain of Responsibility در زندگی روزمرهی ما به وضوح اتفاق میافتد و در هر شرکتی میتوان این الگو را دید، در یک بانک، بیمارستان و ... این الگو وجود دارد، برای مثال در یک بانک، کارمندهای معمولی بانک به بسیاری از درخواست ها پاسخ میدهند، اما درخواستهای خاص مانند وامها به سطح بالاتر برای رسیدگی ارجاع داده میشوند و حتی برای وامهای کلان و برخی دیگر درخواستهای خاص، مدیر بانک باید به درخواست رسیدگی کند و این درخواستها از محدودهی پاسخگویی کارمندان در سطوح پایینتر خارج است. بنابراین حلقهای از عناصر پاسخگو در بانک وجود دارد که بر اساس ماهیت درخواستهای مشتریان عناصر مختلفی پاسخگویی به درخواستها را به عهده میگیرند. همچنین مشاهده میشود که در یک سطح ممکن است چند نفر برای پاسخگویی وجود داشته باشند که همه آنها از امکانات و محدودیتهای مشابه برخوردارند، در حالی که یک بانک تنها یک رئیس دارد اما چندین باجهی پاسخگویی به درخواستهای سطح پایین (که فراوانیه آنها هم از درخواستهای دیگر بیشتر است) وجود دارد و قرار دادن تنها یک باجه سبب تشکیل شدن صفی از مشتریان میشود که باعث خسته شدن مشتریان میگردد و کارایی بانک را بسیار پایین میاورد.
طراحی
نحوهی عملکرد Chain of Responsibility به این صورت است که درخواستی از کاربر مطرح میشود، سپس این الگو درخواست کاربر را در طول زنجیرهی handlerها میگرداند تا handler مناسب برای پاسخگویی به درخواست کاربر پیدا شود. برای آنکه یک handler در زنجیره بتواند به یک درخواست پاسخ دهد باید منابع، دانش و مجوزهای لازم را داشته باشد. Handlerها در سطح خود بر اساس موارد فوق تصمیم میگیرند که میتوانند به یک درخواست پاسخ دهند یا باید درخواست را به عنصر بعدی (یا عنصر در سطح بالاتر) بفرستند.
Chain of Responsibility الگویی ساده است، یک Interface کلی دارد که توسط چندین handler (به تعداد مورد نیاز، یا به تعدادی که میتوانیم ایجاد کنیم) پیاده سازی میشود. هر handler یک لینک به حلقهی بعدی در زنجیره دارد که با توجه به زبانی که مورد استفادهی برنامه نویس قرار میگیرد این لینک ایجاد میگردد تا در صورت لزوم handler درخواست کاربر را به handler بعدی بفرستد. در UML زیر پیاده سازیه Chain of Responsibility بطور ساده نمایش داده شده است.
در این دیاگرام IHandler، interface ای است که handlerها از روی آن را پیاده سازی میشوند، Handler1 و Handler2 دو شی از IHandler Interface هستند که handlerهای تعریف شده برای پاسخ گویی به درخواستهای کاربران میباشند و Successor، Link به شی handler بعدی در زنجیره است. کلاس Client کلاسی است که درخواستها (Requestها) را ایجاد میکند و Request درخواست مطرح شده از سوی کلاس Client است.
و اگر بخواهیم این شکل را با مثال بانک مطابقت دهیم خواهیم داشت:
مشتری = Client
رئیس و کارمندان بانک = Hadlers (Handler1,Handler2)
ارجاع به مسئول بالا رتبه = Successor
درخواست مشتری = Request
چند نکته در طراحی:
- در UML تنها یک Handler در هر سطح از زنجیر تعریف شده است، اما در عمل میتوان چند Handler در هر سطح تعریف کرد که اختیارات یکسانی دارند و به درخواستهای یک سطح پاسخ میدهند(مانند کارمندان بانک).
- نکتهی مهم در طراحی و پیاده سازی Chain of Responsibility آن است که، در صورتی که یک درخواست در طول زنجیر حرکت کرد و هیچ Handler ای قادر به پاسخ گویی به آن درخواست نبود، چه اتفاقی برای درخواست مطرح شده بیافتد، در صورت عدم توجه به این مسئله درخواست مطرح شده از بین میرود بدون آنکه که کاربر (مشتری) متوجه این موضوع گردد و این درحالیست که کاربر همچنان منتظر پاسخ درخواستش میباشد. برای اجتناب از این اتفاق دو رویکرد وجود دارد، میتوان در آخرین سطح زنجیره شی ای قرار داد تا در صورتی که هیچ Handlerای به درخواست پاسخ نداد، آخرین شی زنجیر به آن درخواست یک پاسخ پیش فرض بدهد. رویکرد دیگر آن است که بدون آنکه به درخواست پاسخی بدهیم پیغامی برای کاربر فرستاده شود و او را از غیر قابل قبول بودن درخواستش مطلع سازد.
- از آنجایی که هر عضو زنجیر باید بتواند تشخیص دهد آیا قادر به پاسخ گویی به درخواست کاربر میباشد یا خیر، لازم است ابزار لازم برای این تشخیص در آن ایجاد شده باشد، برای این منظور پیاده سازیهای متفاوتی وجود دارد که به نوع Requestها بستگی دارد. در بعضی موارد بررسی کردن درست بودن یک شرط میتواند کافی باشد.
پیاده سازی
یک مثال ساده به زبان جاوا به شرح زیر را برای نمونه پیاده سازی میکنیم:
فرض کنید برنامهای شامل پنج Handler است که هر سطح تنها یک handler دارد (یعنی دو handler هم سطح نداریم). هر handler به درخواستهایی تا سقف 1000 برابر ID خود میتواند پاسخ دهد. هر handler به handler در سطح بالاتر link دارد و handlerها از سطح بالا به پایین ایجاد میشوند.
public interface Handler {
public String HandleRequest(int data);
}
public class HandlerA implements Handler {
private Handler next;
private int id;
private int limit;
public HandlerA(int id, Handler handler) {
this.id = id;
limit = id * 1000;
next = handler;
}
private int getId() {
return id;
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
public String HandleRequest(int data) {
if (data < limit) {
return "Request for " + data + " handled at level " + getId();
} else if (next != null) {
return next.HandleRequest(data);
} else {
return ("Request for " + data + " handled BY DEFAULT at level " + getId());
}
}
}
public class App {خروجی
public static void main(String[] args) {
Handler start = null;
for (int i = 5; i > 0; i--) {
System.out.println("Handler " + i + " deals up to a limit of " + i * 1000);
start = new HandlerA(i, start);
}
int[] a = {50, 2000, 1500, 10000, 175, 500};
for (int i : a) {
System.out.println(start.HandleRequest(i));
}
}
}
Handler 5 deals up to a limit of 5000
Handler 4 deals up to a limit of 4000
Handler 3 deals up to a limit of 3000
Handler 2 deals up to a limit of 2000
Handler 1 deals up to a limit of 1000
Request for 50 handled at level 1
Request for 2000 handled at level 3
Request for 1500 handled at level 2
Request for 10000 handled BY DEFAULT at level 5
Request for 175 handled at level 1
Request for 500 handled at level 1
مثال پیاده سازی شده با زبان جاوا را میتوانید از github من دانلود کنید.
اگر قبلا در بیان ثبت نام کرده اید لطفا ابتدا وارد شوید، در غیر این صورت می توانید ثبت نام کنید.