سلسلة المسؤوليات.
سلسلة المسؤوليات هي نمط تصميم سلوكي يتيح لك تمرير الطلبات عبر سلسلة من المعالجات. عند استقبال طلب، يقرر كل معالج إما معالجة الطلب أو تمريره إلى المعالج التالي في السلسلة.
##الغرض
سلسلة المسؤوليات هي نمط تصميم سلوكي يتيح لك تمرير الطلبات عبر سلسلة من المعالجات. عند استقبال طلب، يقرر كل معالج إما معالجة الطلب أو تمريره إلى المعالج التالي في السلسلة.

##المشكلة
تخيّل أنك تعمل على نظام طلبات إلكتروني. تريد تقييد الوصول إلى النظام بحيث يتمكن المستخدمون المصادَق عليهم فقط من إنشاء الطلبات. كما يجب أن يتمتع المستخدمون الذين لديهم صلاحيات إدارية بوصول كامل إلى جميع الطلبات.
بعد بعض التخطيط، أدركت أن عمليات التحقق هذه يجب أن تُنفَّذ بالتسلسل. يمكن للتطبيق محاولة مصادقة المستخدم على النظام عند استقبال طلب يحتوي على بيانات اعتماده. ومع ذلك، إذا كانت بيانات الاعتماد غير صحيحة وفشلت المصادقة، فلا داعي للمتابعة مع أي عمليات تحقق أخرى.

يجب أن يمر الطلب بسلسلة من عمليات التحقق قبل أن يتمكن نظام الطلبات نفسه من معالجته.
خلال الأشهر القليلة التالية، نفّذت عدة عمليات تحقق تسلسلية إضافية.
-
اقترح أحد زملائك أنه من غير الآمن تمرير البيانات الخام مباشرة إلى نظام الطلبات. لذا أضفت خطوة تحقق إضافية لتنقية البيانات في الطلب.
-
لاحقًا، لاحظ أحدهم أن النظام عرضة لهجمات تخمين كلمات المرور بالقوة الغاشمة. لتفادي ذلك، أضفت على الفور عملية تحقق تُصفّي الطلبات الفاشلة المتكررة القادمة من نفس عنوان IP.
-
اقترح شخص آخر أنك تستطيع تسريع النظام بإعادة النتائج المخزنة مؤقتًا عند الطلبات المتكررة التي تحتوي على نفس البيانات. لذا أضفت عملية تحقق أخرى تسمح للطلب بالمرور إلى النظام فقط إذا لم يكن هناك استجابة مخزنة مؤقتًا مناسبة.

كلما نما الكود، ازدادت فوضاه.
أصبح كود عمليات التحقق، الذي كان يبدو فوضويًا بالفعل، منتفخًا أكثر فأكثر مع إضافة كل ميزة جديدة. كان تغيير عملية تحقق واحدة يؤثر أحيانًا على الأخريات. والأسوأ من ذلك، أنك عندما حاولت إعادة استخدام عمليات التحقق لحماية مكونات أخرى في النظام، اضطررت إلى تكرار بعض الكود، إذ احتاجت تلك المكونات إلى بعض عمليات التحقق وليس كلها.
أصبح النظام صعب الفهم جدًا ومكلف الصيانة. كافحت مع الكود لبعض الوقت، حتى قررت ذات يوم إعادة هيكلة كل شيء.
##الحل
كغيرها من أنماط التصميم السلوكية، تعتمد سلسلة المسؤوليات على تحويل سلوكيات معينة إلى كائنات مستقلة تُسمى معالجات. في حالتنا، يجب استخراج كل عملية تحقق إلى فئتها الخاصة بطريقة واحدة تُنفّذ التحقق. يتم تمرير الطلب مع بياناته إلى هذه الطريقة كوسيط.
يقترح النمط ربط هذه المعالجات في سلسلة. يحتوي كل معالج مرتبط على حقل لتخزين مرجع إلى المعالج التالي في السلسلة. بالإضافة إلى معالجة الطلب، تُمرّر المعالجات الطلب على طول السلسلة. يتنقل الطلب على طول السلسلة حتى تحصل جميع المعالجات على فرصة لمعالجته.
والأهم أن المعالج يستطيع اتخاذ قرار بعدم تمرير الطلب إلى بقية السلسلة، وبذلك يوقف أي معالجة لاحقة.
في مثال نظام الطلبات، ينفّذ المعالج المعالجة ثم يقرّر ما إذا كان سيمرّر الطلب إلى بقية السلسلة. وبافتراض أن الطلب يحتوي على بيانات صحيحة، يستطيع جميع المعالجين تنفيذ سلوكهم الأساسي، سواء كان التحقق من المصادقة أو التخزين المؤقت.

تصطف المعالجات واحدة تلو الأخرى، مكوّنةً سلسلة.
لكن هناك نهجاً مختلفاً قليلاً، وأقرب إلى الصيغة التقليدية للنمط: عند استلام الطلب، يقرّر المعالج ما إذا كان قادراً على معالجته. وإذا كان قادراً، فإنه لا يمرّره إلى غيره. وهكذا يعالج الطلب معالج واحد فقط، أو لا يعالجه أحد. ويشيع هذا النهج عند التعامل مع الأحداث في تسلسلات عناصر واجهة المستخدم الرسومية.
فمثلاً، عندما ينقر المستخدم زراً، ينتقل الحدث عبر سلسلة من عناصر واجهة المستخدم تبدأ بالزر، ثم تمر بالحاويات التي تضمه، مثل النماذج واللوحات، وتنتهي بالنافذة الرئيسية للتطبيق. ويعالج الحدث أول عنصر في السلسلة قادر على التعامل معه. ويوضّح هذا المثال أيضاً إمكانية استخراج سلسلة من شجرة كائنات.

يمكن تكوين سلسلة من فرع في شجرة كائنات.
من الضروري أن تنفّذ جميع فئات المعالجات الواجهة نفسها. ولا ينبغي لكل معالج ملموس أن يهتم إلا بامتلاك المعالج التالي للأسلوب execute ؛ وبذلك يمكنك تركيب السلاسل أثناء التشغيل باستخدام معالجات مختلفة، دون ربط الكود بفئاتها الملموسة.
##البنية
-
يُعلن المعالج عن الواجهة المشتركة لجميع المعالجات المحددة. عادةً يحتوي على طريقة واحدة فقط لمعالجة الطلبات، لكن أحيانًا قد يحتوي أيضًا على طريقة أخرى لتعيين المعالج التالي في السلسلة.
-
المعالج الأساسي فئة اختيارية يمكنك أن تضع فيها الكود المتكرر المشترك بين جميع فئات المعالجات.
عادةً تُعرّف هذه الفئة حقلًا لتخزين مرجع إلى المعالج التالي. يمكن للعملاء بناء سلسلة بتمرير معالج إلى مُنشئ أو مُحدِّد المعالج السابق. يمكن للفئة أيضًا تنفيذ سلوك المعالجة الافتراضي: يمكنها تمرير التنفيذ إلى المعالج التالي بعد التحقق من وجوده.
-
تحتوي المعالجات المحددة على الكود الفعلي لمعالجة الطلبات. عند استقبال طلب، يجب على كل معالج أن يقرر ما إذا كان سيعالجه، وبالإضافة إلى ذلك، ما إذا كان سيمرره على طول السلسلة.
عادةً ما تكون المعالجات مكتفية بذاتها وغير قابلة للتغيير، وتقبل جميع البيانات الضرورية مرة واحدة فقط عبر المُنشئ.
-
العميل يمكنه تركيب السلاسل مرة واحدة أو تركيبها ديناميكياً وفقاً لمنطق التطبيق. لاحظ أنه يمكن إرسال الطلب إلى أي معالج في السلسلة، وليس بالضرورة إلى المعالج الأول.
##الكود الزائف
في هذا المثال، يتولى نمط سلسلة المسؤوليات عرض معلومات المساعدة السياقية لعناصر واجهة المستخدم الرسومية النشطة.
تُنظّم واجهة المستخدم الرسومية للتطبيق عادةً على هيئة شجرة كائنات. فمثلاً، تكون الفئة Dialog ، التي تعرض النافذة الرئيسية للتطبيق، جذر شجرة الكائنات. ويحتوي مربع الحوار على Panels، وقد تحتوي بدورها على لوحات أخرى أو عناصر بسيطة مثل Buttons و TextFields.
يمكن للمكون البسيط عرض تلميحات سياقية موجزة، طالما أن المكون لديه نص مساعدة مُعيَّن. لكن المكونات الأكثر تعقيدًا تُحدد طريقتها الخاصة لعرض المساعدة السياقية، كعرض مقتطف من الدليل أو فتح صفحة في المتصفح.

هكذا ينتقل طلب المساعدة عبر كائنات واجهة المستخدم الرسومية.
عندما يوجّه المستخدم مؤشر الفأرة إلى عنصر ويضغط على المفتاح F1 ، يحدّد التطبيق المكوّن الموجود تحت المؤشر ويرسل إليه طلب مساعدة. يصعد الطلب عبر الحاويات التي تضم العنصر حتى يصل إلى عنصر قادر على عرض معلومات المساعدة.
// تُعلن واجهة المعالج عن طريقة لتنفيذ
// طلب.
interface ComponentWithContextualHelp is
method showHelp()
// الفئة الأساسية للمكونات البسيطة.
abstract class Component implements ComponentWithContextualHelp is
field tooltipText: string
// يعمل حاوي المكون كالرابط التالي في
// سلسلة المعالجات.
protected field container: Container
// يعرض المكون تلميحًا إذا كان هناك نص مساعدة
// مُعيَّن له. وإلا يُعيد توجيه الاستدعاء إلى
// الحاوي، إن وُجد.
method showHelp() is
if (tooltipText != null)
// عرض التلميح.
else
container.showHelp()
// يمكن للحاويات احتواء مكونات بسيطة وحاويات أخرى
// كعناصر أبناء. علاقات السلسلة مُنشأة هنا.
// ترث الفئة سلوك showHelp من
// أصلها.
abstract class Container extends Component is
protected field children: array of Component
method add(child) is
children.add(child)
child.container = this
// قد تكتفي المكونات الأولية بتنفيذ المساعدة الافتراضي...
class Button extends Component is
// ...
// لكن يمكن للمكونات المعقدة تجاوز التنفيذ الافتراضي.
// إذا تعذّر توفير نص المساعدة بطريقة جديدة،
// يمكن للمكون دائمًا استدعاء التنفيذ الأساسي
// (راجع فئة Component).
class Panel extends Container is
field modalHelpText: string
method showHelp() is
if (modalHelpText != null)
// عرض نافذة منبثقة مع نص المساعدة.
else
super.showHelp()
// ...مثل ما سبق...
class Dialog extends Container is
field wikiPageURL: string
method showHelp() is
if (wikiPageURL != null)
// فتح صفحة مساعدة wiki.
else
super.showHelp()
// كود العميل.
class Application is
// يُهيئ كل تطبيق السلسلة بطريقة مختلفة.
method createUI() is
dialog = new Dialog("Budget Reports")
dialog.wikiPageURL = "http://..."
panel = new Panel(0, 0, 400, 800)
panel.modalHelpText = "This panel does..."
ok = new Button(250, 760, 50, 20, "OK")
ok.tooltipText = "This is an OK button that..."
cancel = new Button(320, 760, 50, 20, "Cancel")
// ...
panel.add(ok)
panel.add(cancel)
dialog.add(panel)
// تخيّل ما يحدث هنا.
method onF1KeyPress() is
component = this.getComponentAtMouseCoords()
component.showHelp()
##متى يُستخدم
استخدم نمط سلسلة المسؤوليات عندما يُتوقَّع من برنامجك معالجة أنواع مختلفة من الطلبات بطرق متنوعة، لكن الأنواع الدقيقة للطلبات وتسلسلها غير معروفة مسبقًا.
يتيح النمط ربط عدة معالجات في سلسلة واحدة، وعند استقبال طلب، "سؤال" كل معالج عمّا إذا كان بمقدوره معالجته. وبهذه الطريقة تحصل جميع المعالجات على فرصة لمعالجة الطلب.
استخدم النمط عندما يكون من الضروري تنفيذ عدة معالجات بترتيب معين.
نظرًا لأنك تستطيع ربط المعالجات في السلسلة بأي ترتيب، فإن جميع الطلبات ستمر عبر السلسلة تمامًا كما خططت.
استخدم نمط CoR عندما يُفترض أن تتغير مجموعة المعالجات وترتيبها أثناء التشغيل.
إذا وفّرت مُحدِّدات (setters) لحقل المرجع داخل فئات المعالجات، فستتمكن من إدراج المعالجات وإزالتها وإعادة ترتيبها ديناميكيًا.
##كيفية التنفيذ
-
أعلن عن واجهة المعالج وصِف توقيع الطريقة اللازمة لمعالجة الطلبات.
حدّد كيف سيمرر العميل بيانات الطلب إلى الطريقة. الطريقة الأكثر مرونة هي تحويل الطلب إلى كائن وتمريره إلى طريقة المعالجة كوسيط.
-
للتخلص من الكود المتكرر في المعالجات المحددة، قد يكون من المفيد إنشاء فئة معالج أساسية مجردة مشتقة من واجهة المعالج.
يجب أن تحتوي هذه الفئة على حقل لتخزين مرجع إلى المعالج التالي في السلسلة. فكّر في جعل الفئة غير قابلة للتغيير. ومع ذلك، إذا كنت تخطط لتعديل السلاسل أثناء التشغيل، فتحتاج إلى تعريف مُحدِّد لتغيير قيمة حقل المرجع.
يمكنك أيضًا تنفيذ السلوك الافتراضي المريح لطريقة المعالجة، وهو إعادة توجيه الطلب إلى الكائن التالي ما لم يبقَ أي كائن. ستتمكن المعالجات المحددة من استخدام هذا السلوك باستدعاء الطريقة الأصل.
-
قم بإنشاء فئات فرعية محددة للمعالجات واحدة تلو الأخرى ونفّذ طرق المعالجة الخاصة بها. يجب على كل معالج اتخاذ قرارين عند استقبال طلب:
- ما إذا كان سيعالج الطلب.
- ما إذا كان سيمرر الطلب على طول السلسلة.
-
قد يجمع العميل السلاسل بنفسه أو يتلقاها مُجمَّعة مسبقًا من كائنات أخرى. في الحالة الأخيرة، يجب تنفيذ بعض فئات المصنع لبناء السلاسل وفق الإعدادات أو متطلبات البيئة.
-
يمكن للعميل تشغيل أي معالج في السلسلة، وليس فقط الأول. سيتم تمرير الطلب على طول السلسلة حتى يرفض أحد المعالجات تمريره أو حتى يصل إلى نهاية السلسلة.
-
بسبب الطبيعة الديناميكية للسلسلة، يجب أن يكون العميل مستعدًا للتعامل مع السيناريوهات التالية:
- قد تتكون السلسلة من رابط واحد فقط.
- قد لا تصل بعض الطلبات إلى نهاية السلسلة.
- قد تصل طلبات أخرى إلى نهاية السلسلة دون معالجة.
##المزايا والعيوب
- يمكنك التحكم في ترتيب معالجة الطلبات.
- مبدأ المسؤولية الواحدة. يمكنك فصل الفئات التي تستدعي العمليات عن الفئات التي تنفذها.
- مبدأ الفتح/الإغلاق. يمكنك تقديم معالجات جديدة في التطبيق دون كسر كود العميل الحالي.
- قد تنتهي بعض الطلبات دون معالجة.
##العلاقات مع الأنماط الأخرى
-
سلسلة المسؤوليات, الأمر, الوسيط و المراقب تتناول طرقًا مختلفة لربط مُرسِلي الطلبات ومستقبليها:
- سلسلة المسؤوليات تُمرّر طلبًا بشكل تسلسلي عبر سلسلة ديناميكية من المستقبلين المحتملين حتى يعالجه أحدهم.
- الأمر يُنشئ اتصالات أحادية الاتجاه بين المُرسِلين والمستقبلين.
- الوسيط يُلغي الاتصالات المباشرة بين المُرسِلين والمستقبلين، مُجبرًا إياهم على التواصل بشكل غير مباشر عبر كائن وسيط.
- المراقب يتيح للمستقبلين الاشتراك وإلغاء الاشتراك ديناميكيًا من استقبال الطلبات.
-
سلسلة المسؤوليات يُستخدم غالبًا بالتزامن مع المركّب. في هذه الحالة، عندما يحصل مكون ورقي على طلب، قد يمرره عبر سلسلة جميع المكونات الأصلية وصولًا إلى جذر شجرة الكائنات.
-
يمكن تنفيذ معالجات سلسلة المسؤوليات على هيئة الأوامر. في هذه الحالة، يمكنك تنفيذ الكثير من العمليات المختلفة على نفس كائن السياق، الذي يمثله طلب.
لكن هناك نهجاً آخر يكون فيه الطلب نفسه كائناً من نمط الأمر ؛ وفي هذه الحالة، يمكنك تنفيذ العملية نفسها في سلسلة من السياقات المختلفة المرتبطة معاً.
-
سلسلة المسؤوليات و المزخرف لديهما هياكل فئات متشابهة جدًا. يعتمد كلا النمطين على التركيب العودي لتمرير التنفيذ عبر سلسلة من الكائنات. ومع ذلك، توجد عدة اختلافات جوهرية.
سلسلة المسؤوليات يمكن لمعالجات هذا النمط تنفيذ عمليات مختلفة بصورة مستقلة، كما يمكنها إيقاف تمرير الطلب في أي وقت. في المقابل، تستطيع المزخرفات توسيع سلوك الكائن مع الحفاظ على توافقه مع الواجهة الأساسية. كذلك لا يُسمح للمزخرفات بقطع تدفق الطلب.