الفرق بين المراجعتين لصفحة: «Design Patterns/mediator»
أسامه-دمراني (نقاش | مساهمات) 2.1 محتوى |
جميل-بيلوني (نقاش | مساهمات) طلا ملخص تعديل |
||
(11 مراجعة متوسطة بواسطة مستخدمين اثنين آخرين غير معروضة) | |||
سطر 3: | سطر 3: | ||
== المشكلة == | == المشكلة == | ||
لنقل أن لديك صندوقًا حواريًا لإنشاء وتعديل حسابات المستخدمين، به عناصر مختلفة من متحكمات الاستمارات، مثل الحقول النصية ومربعات الاختيار والأزرار وغيرها. | [[ملف:dpm.problem1.png|تصغير|(ش.1) قد تصبح العلاقات بين عناصر واجهة المستخدم فوضوية كلما تطور البرنامج.]] | ||
لنقل أن لديك صندوقًا حواريًا لإنشاء وتعديل حسابات المستخدمين، به عناصر مختلفة من متحكمات الاستمارات، مثل الحقول النصية ومربعات الاختيار والأزرار وغيرها. (انظر ش.1) | |||
[[ملف:dpm.problem2.png|تصغير|(ش.2) قد يكون لديك علاقات كثيرة بين العناصر وبعضها، ومن ثم فإن إجراء تغييرات على بعض العناصر قد يؤثر في غيرها.]] | |||
قد تتفاعل بعض عناصر الاستمارة مع بعضها البعض، فتحديد المربع أمام خيار "لدي كلب" مثلًا قد يظهر حقلًا نصيًا مخفيًا لإدخال اسم ذاك الكلب، والنقر على زر "إرسال" يستدعي تحققه من جميع القيم المُدخلة في الاستمارة قبل حفظ البيانات. (انظر ش.2) | |||
وبتطبيق هذا المنطق مباشرة داخل شيفرة عناصر الاستمارة فإنك تجعل فئات تلك العناصر أصعب في إعادة الاستخدام في استمارات أخرى داخل التطبيق، فقد لا تتمكن من استخدام فئة مربع الاختيار تلك داخل استمارة أخرى بسبب أنك ربطتها بالحقل النصي الذي سيظهر بعد تحديد مربع الاختيار منتظرًا إدخالك لنص "اسم الكلب في هذه الحالة من المثال السابق". فيكون الخيار الذي لديك هو أن تستخدم كل تلك الفئات التي استخدمتها لإخراج حساب المستخدم، أو تتركها بالكلية. | |||
== الحل == | |||
يقترح نمط الوسيط أنك يجب أن توقف جميع الاتصالات المباشرة بين العناصر التي تريدها أن تكون مستقلة بنفسها، وتجعلها تتواصل فيما بينها بشكل غير مباشر من خلال استدعاء كائن وسيط خاص يعيد توجيه الاستدعاءات إلى العناصر المناسبة. وهكذا تعتمد العناصر على فئة وسيط واحدة بدلًا من أن تكون مرتبطة بعشرات العناصر الأخرى. (انظر ش.3) | |||
[[ملف:dpm.solution1.png|تصغير|(ش.3) عناصر الواجهة الرسومية يجب أن تتواصل بشكل غير مباشر من خلال كائن وسيط.]] | |||
وفي مثالنا الخاص باستمارة تعديل الحساب فإن فئة الصندوق الحواري نفسها قد تتصرف كوسيط، وهي على الأرجح تعرف جميع عناصرها الفرعية، لذا فلن تحتاج أن تدخِل اعتماديات جديدة في تلك الفئة. | |||
لعل أكثر تغيير تلاحظه هنا هو ذاك الذي يحدث لعناصر الاستمارة الحقيقية، فمثلًا ما كان يحدث عادة عند النقر على زر "إرسال Submit" أن الزر ينفذ عملية تحقق كاملة لكل القيم التي أدخلها المستخدم، وذلك في كل مرة يضغط فيها زر إرسال. لكن بعد جعله مرتبطًا بالصندوق الحواري فقط، فإن وظيفته الوحيدة صارت تنبيه الصندوق الحواري بنقرة المستخدم، وينفذ الصندوق بعدها عمليات التحقق أو يمررها إلى العناصر المنفردة، ومن ثم فإن الزر يعتمد الآن على فئة الصندوق الحواري وحدها بدلًا من ارتباطه بعشرات العناصر من الاستمارة. | |||
تستطيع المضي في هذا المنطق وتجعل الاعتمادية أكثر مرونة باستخراج الواجهة المشتركة لكل أنواع الصناديق الحوارية، وينبغي أن تصرح الواجهة عن أسلوب الإشعار الذي ستستخدمه كل عناصر الاستمارة لإشعار الصندوق الحواري بالأحداث التي تقع لتلك العناصر، وعليه ينبغي أن يستطيع زر الإرسال الآن أن يعمل مع أي صندوق حواري يستخدم تلك الواجهة. وهكذا يسمح لك نمط الوسيط بتغليف شبكة علاقات بين عدة كائنات داخل كائنِ وسيطٍ واحد، وفائدة هذا أنه كلما قلت الاعتماديات (dependencies) في فئة ما، صار تعديلها أسهل، وكذلك توسيعها وإعادة استخدامها. | |||
== مثال من الواقع == | |||
[[ملف:dpm.live-example.png|تصغير|(ش.4) لا يتواصل الطيارون مباشرة مع بعضهم لتحديد أيهم يهبط على المدرج أولًا، بل تتم عمليات التواصل من خلال برج المراقبة.]] | |||
لا يتواصل الطيارون الذين على وشك الهبوط أو المغادرة مباشرة مع بعضهم لتحديد من يهبط قبل من ومن يقلع قبل غيره، بل يخاطبون مسؤولًا في حركة المرور الخاصة بالطائرات على مدرجات المطار وفي سمائه، يجلس في برج طويل على بعد مناسب من المدرجات. لكن إن لم يوجد ذلك المسؤول فسيستغرق الطيارون وقتًا طويلًا في مناقشة أولويات الهبوط وأماكنه إضافة إلى ضرورة علمهم بكل طائرة موجودة في محيط المطار أو تقترب منه ، مما يزيد من معدلات الحوادث بشكل مخيف في النهاية. | |||
وتنتهي مهمة برج المراقبة بعد ضبط عمليات الهبوط والإقلاع، فلا يتحكم بشيء من رحلة الطائرة بعد ذلك، إذ أنه موجود لتنفيذ قوانين تحكم حركة مرور الطائرات من وإلى وعلى المدرجات، حيث يكون عدد المشاركين في تلك العملية كبيرًا على طيار بمفرده داخل طائرته. | |||
== البنية == | |||
[[ملف:dpm.structure-indexed.png|تصغير| | |||
(ش.5) | |||
]] | |||
# '''العناصر (Components)''' هي فئات مختلفة تحتوي على بعض منطق العمل (Business Logic)، وكل عنصر به مرجع إلى وسيط (mediator)، صُرِّح عنه مع نوع واجهة الوسيط. ولا يشعر العنصر بفئة الوسيط الحقيقية، لهذا تستطيع إعادة استخدام العنصر في برامج أخرى من خلال ربطها بوسيط مختلف. | |||
# واجهة '''الوسيط (Mediator)''' تصرح عن أساليب للتواصل مع العناصر، وتتضمن عادة أسلوب إشعار واحد. وقد تمرر العناصر أي سياق كوسائط (arguments) لهذا الأسلوب، بما في ذلك كائناتها الخاصة، لكن بطريقة لا تسمح بحدوث تكرار بين العنصر المستقبِل وفئة المرسِل. | |||
# '''الوسيط الحقيقي (Concrete Mediator)''' يختزل العلاقات بين العناصر المختلفة، ويحتفظ الوسيط الحقيقي عادة بمراجع إلى كل العناصر التي يديرها، وأحيانًا يدير دورة حياتها كذلك. | |||
# يجب ألا تشعر العناصر بوجود العناصر الأخرى، فإن حدث شيء مهم لعنصر أو حدث داخله، فيجب أن ينبه الوسيط حصرًا، ويستطيع الوسيط التعرف على المرسل بمجرد استلام الإشعار، وهذا يكون كافيًا في الغالب لتقرير أي عنصر يجب أن يُشغَّل في المقابل. | |||
لتقريب الصورة، فإن الأمر يبدو من منظور العنصر كأنه صندوق أسود، فلا يعرف المرسل من سيتعامل مع طلبه، ولا يعرف المستقبِل من أرسل الطلب أصلًا. | |||
== مثال وهمي == | |||
[[ملف:dpm.example.png|بدون|تصغير|(ش.6) الصورة. هيكل لفئات صندوق حواري في واجهة رسومية.]] | |||
يساعدك نمط الوسيط في هذا المثال على التخلص من الاعتماديات المزدوجة بين فئات الواجهة الرسومية المختلفة والأزرار ومربعات الاختيار والحقول النصية كذلك. | |||
لا يتواصل العنصر الذي يشغِّله المستخدم مع العناصر الأخرى بشكل مباشر حتى لو بدا أنه يفترض به ذلك، ولا يحتاج سوى أن ينبه الوسيط الخاص به بذلك الحدث، مرسلًا أي بيانات سياقية أخرى مع ذلك التنبيه. | |||
وفي هذا المثال يتصرف الصندوق الحواري للاستيثاق كوسيط، فهو يعرف كيف يجب أن تتعاون العناصر الحقيقية، ومن ثم ييسر عملية التواصل غير المباشر. ويقرر الصندوق عند استلام تنبيه بحدثٍ أيَّ العناصر التي ستتعامل مع ذلك الحدث، ومن ثم يعيد توجيه الاستدعاء وفقًا لذلك.<syntaxhighlight lang="java"> | |||
// تصرح واجهة الوسيط عن أسلوب تستخدمه العناصر لتنبيه الوسيط بالأحداث | |||
// المختلفة، وقد يتفاعل الوسيط مع تلك الأحداث ويمرر التنفيذ إلى عناصر | |||
// أخرى. | |||
interface Mediator is | |||
method notify(sender: Component, event: string) | |||
// فئة الوسيط الحقيقي. الشبكة المتداخلة من الاتصالات بين العناصر | |||
// الفردية قد حُلَّت ونُقلت إلى الوسيط. | |||
class AuthenticationDialog implements Mediator is | |||
private field title: string | |||
private field loginOrRegisterChkBx: Checkbox | |||
private field loginUsername, loginPassword: Textbox | |||
private field registrationUsername, registrationPassword | |||
private field registrationEmail: Textbox | |||
private field okBtn, cancelBtn: Button | |||
constructor AuthenticationDialog() is | |||
// أنشئ جميع كائنات العناصر ومرر الوسيط الحالي إلى منشئاتها | |||
// لتحقيق الروابط. | |||
// حين يحدث شيء ما لعنصر ما فإنه ينبه الوسيط، وعندما يستلم الوسيط | |||
// التنبيه فقد يتصرف بنفسه أو يمرر الطلب إلى مكون آخر. | |||
method notify(sender, event) is | |||
if (sender == loginOrRegisterChkBx and event == "check") | |||
if (loginOrRegisterChkBx.checked) | |||
title = "Log in" | |||
// 1. أظهر عناصر استمارة تسجيل الدخول. | |||
// 2. أخْفِ عناصر استمارة التسجيل. | |||
else | |||
title = "Register" | |||
// 1. أظهر عناصر استمارة تسجيل الدخول. | |||
// 2. أخْفِ عناصر استمارة التسجيل. | |||
if (sender == okBtn && event == "click") | |||
if (loginOrRegister.checked) | |||
// حاول إيجاد مستخدم عن طريق بيانات تسجيل الدخول. | |||
if (!found) | |||
// أظهر رسالة خطأ فوق حقل تسجيل الدخول. | |||
else | |||
// 1. أنشئ حساب مستخدم باستخدام بيانات من حقول التسجيل. | |||
// 2. سجِّل دخول هذا المستخدم. | |||
// ... | |||
// تتواصل العناصر مع الوسيط باستخدام واجهة الوسيط. | |||
// ويمكنك استخدام نفس العناصر في سياقات أخرى من خلال | |||
// ربطها بكائنات وسيط مختلفة. | |||
class Component is | |||
field dialog: Mediator | |||
constructor Component(dialog) is | |||
this.dialog = dialog | |||
method click() is | |||
dialog.notify(this, "click") | |||
method keypress() is | |||
dialog.notify(this, "keypress") | |||
// لا تتواصل العناصر الحقيقية مع بعضها، وليس لها سوى قناة اتصال وحيدة | |||
// وهي إرسال التنبيهات إلى الوسيط. | |||
class Button extends Component is | |||
// ... | |||
class Textbox extends Component is | |||
// ... | |||
class Checkbox extends Component is | |||
method check() is | |||
dialog.notify(this, "check") | |||
// ... | |||
</syntaxhighlight> | |||
== قابلية الاستخدام == | |||
* '''استخدم نمط الوسيط عندما يكون من الصعب تغيير بعض الفئات بسبب أن لها ارتباطات كثيرة مع فئات أخرى.''' | |||
يسمح لك النمط باستخراج جميع العلاقات بين الفئات إلى فئة منفصلة عازلًا أي تغييرات على عنصر بعينه عن بقية الشيفرة. | |||
* '''استخدم النمط عندما لا تستطيع إعادة استخدام عنصر في برنامج مختلف بسبب أنه يعتمد بشدة على عناصر أخرى.''' | |||
تصبح العناصر المنفردة بعد تطبيق نمط الوسيط غير مدركة للعناصر الأخرى رغم أنها تستطيع التواصل بين بعضها البعض -ولو كان بشكل غير مباشر- من خلال كائن وسيط. ولكي تعيد استخدام عنصر في برنامج مختلف فإنك تحتاج إلى إضافة فئة وسيط جديدة إليه. | |||
* '''استخدم الوسيط حين تنشئ فئات فرعية كثيرة لعناصر من أجل إعادة استخدام بعض السلوك الأساسي (basic behavior) في سياقات متعددة.''' | |||
بما أن جميع العلاقات بين العناصر محتواة داخل الوسيط، فمن السهل تحديد طرق جديدة كليًا لتعاون تلك العناصر من خلال إدخال فئات وسيط جديدة دون الحاجة إلى تغيير العناصر أنفسها. | |||
== كيفية الاستخدام == | |||
# حدد مجموعة فئات مرتبطة بشدة ببعضها ستستفيد من كونها مستقلة (من أجل سهولة الصيانة مثلًا أو إعادة استخدام بسيطة لتلك الفئات). | |||
# صرِّح عن واجهة الوسيط وَصِفْ بروتوكول التواصل الذي تريده بين الوسطاء والعناصر الأخرى، ويكفي في أغلب الحالات أسلوب واحد لاستقبال التنبيهات من العناصر. هذه الواجهة ضرورية عندما تريد إعادة استخدام فئات العناصر في سياقات مختلفة، فطالما أن العنصر يعمل مع وسيطه من خلال واجهة عامة (generic) فيمكنك ربط ذلك العنصر مع استخدام مختلف للوسيط. | |||
# استخدم فئة الوسيط الحقيقية، ستستفيد تلك الفئة من تخزين مراجع إلى كل العناصر التي تديرها. | |||
# تستطيع جعل الوسيط مسؤولًا عن إنشاء وإلغاء كائنات العنصر، وعندها قد يتشابه الوسيط مع نمط المصنع المجرد أو الواجهة. | |||
# يجب أن تخزن العناصر مرجعًا إلى كائن الوسيط، ويتم الاتصال غالبًا في منشئ العنصر حيث يُمرَّر كائن الوسيط كمُعطى (argument). | |||
# غيِّر شيفرة العناصر بحيث تستدعي أسلوب الإشعار الخاص بالوسيط بدلًا من الأساليب التي في العناصر الأخرى. أيضًا، استخرج الشيفرة التي تضمن استدعاء عناصر أخرى إلى داخل فئة الوسيط، ونفذ هذه الشيفرة كلما استقبل الوسيط إشعارات من ذلك العنصر. | |||
== المزايا والعيوب == | |||
=== المزايا === | |||
* مبدأ المسؤولية الواحدة. تستطيع استخراج عمليات التواصل بين العناصر المختلفة إلى مكان واحد، مما ييسر استيعابها وصيانتها. | |||
* مبدأ المفتوح/المغلق. تستطيع إدخال وسطاء جدد دون الحاجة إلى تغيير العناصر الحقيقية. | |||
* تستطيع تقليل الارتباط (coupling) بين العناصر المختلفة لبرنامج ما. | |||
* تستطيع إعادة استخدام العناصر المنفردة بشكل أسهل. | |||
=== العيوب === | |||
* قد يتطور الوسيط مع الوقت إلى كائن إلهي (god object). | |||
== العلاقات مع الأنماط الأخرى == | |||
* تقدم أنماط [[Design Patterns/chain of responsibility|سلسلة المسؤولية]] و<nowiki/>[[Design Patterns/command|الأمر]] و<nowiki/>[[Design Patterns/mediator|الوسيط]] و<nowiki/>[[Design Patterns/observer|المراقب]] طرقًا مختلفة لتوصيل مرسِلات الطلبات ومستقبلاتها ببعضها، وذلك على النحو التالي: | |||
# تمرر سلسلة المسؤولية الطلب بشكل تسلسلي مع سلسلة ديناميكية من المستقبلات المحتملة إلى أن يعالجها أحد المستقبلات. | |||
# ينشئ الأمر اتصالات أحادية الاتجاه بين المرسِلات والمستقبِلات. | |||
# يزيل الوسيط الاتصالات المباشرة بين المرسِلات والمستقبلات مجبرًا إياها على التواصل بشكل غير مباشر من خلال كائن وسيط. | |||
# يسمح المراقب للمستقبلات بالاشتراك في استقبال الطلبات وكذلك إلغاء الاشتراك بمرونة. | |||
* يتشابه نمطا [[Design Patterns/facade|الواجهة]] و<nowiki/>[[Design Patterns/mediator|الوسيط]] في الوظائف، فكلاهما يحاول تنظيم التعاون بين فئات كثيرة مرتبطة ببعضها، وذلك على النحو التالي: | |||
# يحدد نمط الواجهة واجهة مبسطة لنظام فرعي من الكائنات، لكنه لا يقدم أي وظيفة جديدة، بل إن النظام الفرعي نفسه غير مدرك لوجود الواجهة، وتستطيع الكائنات داخل النظام الفرعي أن تتواصل بشكل مباشر. | |||
# يمركز الوسيط الاتصالات بين عناصر النظام، وتدرك تلك العناصر وجود كائن الوسيط، ولا تتواصل بين بعضها بشكل مباشر. | |||
* عادة ما يكون الفرق بين نمطي الوسيط و<nowiki/>[[Design Patterns/observer|المراقب]] محيرًا، ففي أغلب الحالات تستطيع استخدام نمط واحد فحسب منهما، لكن في ظروف تشغيل أخرى قد تستطيع استخدام كليهما معًا، وذلك على النحو التالي: | |||
الهدف الأساسي من الوسيط هو إزالة الاعتماديات المتبادلة (mutual dependencies) بين مجموعة من عناصر النظام، وتصبح تلك العناصر معتمدة على كائن وسيط واحد. أما الهدف من المراقب هو إنشاء اتصالات مرنة أحادية الاتجاه بين الكائنات، حيث تتصرف بعض الكائنات كتوابع لغيرها. | |||
ولدينا استخدام شائع لنمط الوسيط يعتمد فيه على نمط المراقب، يلعب فيه كائن الوسيط دور الناشر (publisher)، ويتصرف كل عنصر كمشترك يستطيع الاشتراك في أحداث الوسيط (mediator events) وكذلك إلغاء الاشتراك منها، وتطبيق نمط الوسيط بهذا الشكل يجعله قريب الشبه جدًا من نمط المراقب. تذكر أنك تستطيع استخدام نمط الوسيط بطرق أخرى، فيمكنك مثلًا ربط كل العناصر إلى نفس الكائن الوسيط، ورغم أن هذا الاستخدام لا يشبه نمط المراقب لكن لا يزال صورة من نمط الوسيط. | |||
وتخيل الآن برنامجًا تلعب فيه كل العناصر دور الناشر، سامحة باتصالات مرنة بين بعضها البعض، عندئذ لن يكون هناك كائن وسيط مركزي، بل مجموعة موزعة من المراقبين. | |||
== الاستخدام في لغة جافا == | |||
'''المستوى:''' ★ ★ ☆ | |||
'''الانتشار:''' ★ ★ ☆ | |||
'''أمثلة الاستخدام:''' لعل تسهيل التواصل بين عناصر الواجهة الرسومية لبرنامج ما هو ـكثر مثال منتشر لنمط الوسيط في لغة جافا. ويكون مرادف الوسيط هو الجزء المتحكم في نمط MVC. إليك بعض الأمثلة على النمط في مكتبات جافا: | |||
* [https://docs.oracle.com/javase/8/docs/api/java/util/Timer.html java.util.Timer] (جميع أساليب <code>()schedualXXX</code>). | |||
* [https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable- ()java.util.concurrent.Executor#execute]. | |||
* [https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html java.util.concurrent.ExecutorService] (أساليب <code>()invokeXXX</code> و<code>()submit</code>). | |||
* [https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ScheduledExecutorService.html java.util.concurrent.ScheduledExecutorService] (جميع أساليب <code>()schedualXXX</code>). | |||
* [https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Method.html#invoke-java.lang.Object-java.lang.Object...- ()java.lang.reflect.Method#invoke]. | |||
=== تطبيق المذكرة === | |||
يوضح هذا المثال كيفية تنظيم كثير من عناصر الواجهة الرسومية كي تتعاون بمساعدة الوسيط بدون أن تعتمد على بعضها البعض. | |||
==== العناصر: الفئات الزميلة colleague classes ==== | |||
===== components/Component.java ===== | |||
<syntaxhighlight lang="java"> | |||
package refactoring_guru.mediator.example.components; | |||
import refactoring_guru.mediator.example.mediator.Mediator; | |||
/** | |||
* واجهة العنصر المشتركة. | |||
*/ | |||
public interface Component { | |||
void setMediator(Mediator mediator); | |||
String getName(); | |||
} | |||
</syntaxhighlight> | |||
===== components/AddButton.java ===== | |||
<syntaxhighlight lang="java"> | |||
package refactoring_guru.mediator.example.components; | |||
import refactoring_guru.mediator.example.mediator.Mediator; | |||
import refactoring_guru.mediator.example.mediator.Note; | |||
import javax.swing.*; | |||
import java.awt.event.ActionEvent; | |||
/** | |||
* لا تتواصل العناصر الحقيقية مع بعضها البعض، والطريقة الوحيدة للتواصل | |||
* هي عبر إرسال الطلبات إلى الوسيط. | |||
*/ | |||
public class AddButton extends JButton implements Component { | |||
private Mediator mediator; | |||
public AddButton() { | |||
super("Add"); | |||
} | |||
@Override | |||
public void setMediator(Mediator mediator) { | |||
this.mediator = mediator; | |||
} | |||
@Override | |||
protected void fireActionPerformed(ActionEvent actionEvent) { | |||
mediator.addNewNote(new Note()); | |||
} | |||
@Override | |||
public String getName() { | |||
return "AddButton"; | |||
} | |||
} | |||
</syntaxhighlight> | |||
===== components/DeleteButton.java ===== | |||
<syntaxhighlight lang="text"> | |||
package refactoring_guru.mediator.example.components; | |||
import refactoring_guru.mediator.example.mediator.Mediator; | |||
import javax.swing.*; | |||
import java.awt.event.ActionEvent; | |||
/** | |||
* لا تتواصل العناصر الحقيقية مع بعضها البعض، والطريقة الوحيدة للتواصل | |||
* هي عبر إرسال الطلبات إلى الوسيط. | |||
*/ | |||
public class DeleteButton extends JButton implements Component { | |||
private Mediator mediator; | |||
public DeleteButton() { | |||
super("Del"); | |||
} | |||
@Override | |||
public void setMediator(Mediator mediator) { | |||
this.mediator = mediator; | |||
} | |||
@Override | |||
protected void fireActionPerformed(ActionEvent actionEvent) { | |||
mediator.deleteNote(); | |||
} | |||
@Override | |||
public String getName() { | |||
return "DelButton"; | |||
} | |||
} | |||
</syntaxhighlight> | |||
===== components/Filter.java ===== | |||
<syntaxhighlight lang="java"> | |||
package refactoring_guru.mediator.example.components; | |||
import refactoring_guru.mediator.example.mediator.Mediator; | |||
import refactoring_guru.mediator.example.mediator.Note; | |||
import javax.swing.*; | |||
import java.awt.event.KeyEvent; | |||
import java.util.ArrayList; | |||
/** | |||
* لا تتواصل العناصر الحقيقية مع بعضها البعض، والطريقة الوحيدة للتواصل | |||
* هي عبر إرسال الطلبات إلى الوسيط. | |||
*/ | |||
public class Filter extends JTextField implements Component { | |||
private Mediator mediator; | |||
private ListModel listModel; | |||
public Filter() {} | |||
@Override | |||
public void setMediator(Mediator mediator) { | |||
this.mediator = mediator; | |||
} | |||
@Override | |||
protected void processComponentKeyEvent(KeyEvent keyEvent) { | |||
String start = getText(); | |||
searchElements(start); | |||
} | |||
public void setList(ListModel listModel) { | |||
this.listModel = listModel; | |||
} | |||
private void searchElements(String s) { | |||
if (listModel == null) { | |||
return; | |||
} | |||
if (s.equals("")) { | |||
mediator.setElementsList(listModel); | |||
return; | |||
} | |||
ArrayList<Note> notes = new ArrayList<>(); | |||
for (int i = 0; i < listModel.getSize(); i++) { | |||
notes.add((Note) listModel.getElementAt(i)); | |||
} | |||
DefaultListModel<Note> listModel = new DefaultListModel<>(); | |||
for (Note note : notes) { | |||
if (note.getName().contains(s)) { | |||
listModel.addElement(note); | |||
} | |||
} | |||
mediator.setElementsList(listModel); | |||
} | |||
@Override | |||
public String getName() { | |||
return "Filter"; | |||
} | |||
} | |||
</syntaxhighlight> | |||
===== components/List.java ===== | |||
<syntaxhighlight lang="java"> | |||
package refactoring_guru.mediator.example.components; | |||
import refactoring_guru.mediator.example.mediator.Mediator; | |||
import refactoring_guru.mediator.example.mediator.Note; | |||
import javax.swing.*; | |||
/** | |||
* لا تتواصل العناصر الحقيقية مع بعضها البعض، والطريقة الوحيدة للتواصل | |||
* هي عبر إرسال الطلبات إلى الوسيط. | |||
*/ | |||
@SuppressWarnings("unchecked") | |||
public class List extends JList implements Component { | |||
private Mediator mediator; | |||
private final DefaultListModel LIST_MODEL; | |||
public List(DefaultListModel listModel) { | |||
super(listModel); | |||
this.LIST_MODEL = listModel; | |||
setModel(listModel); | |||
this.setLayoutOrientation(JList.VERTICAL); | |||
Thread thread = new Thread(new Hide(this)); | |||
thread.start(); | |||
} | |||
@Override | |||
public void setMediator(Mediator mediator) { | |||
this.mediator = mediator; | |||
} | |||
public void addElement(Note note) { | |||
LIST_MODEL.addElement(note); | |||
int index = LIST_MODEL.size() - 1; | |||
setSelectedIndex(index); | |||
ensureIndexIsVisible(index); | |||
mediator.sendToFilter(LIST_MODEL); | |||
} | |||
public void deleteElement() { | |||
int index = this.getSelectedIndex(); | |||
try { | |||
LIST_MODEL.remove(index); | |||
mediator.sendToFilter(LIST_MODEL); | |||
} catch (ArrayIndexOutOfBoundsException ignored) {} | |||
} | |||
public Note getCurrentElement() { | |||
return (Note)getSelectedValue(); | |||
} | |||
@Override | |||
public String getName() { | |||
return "List"; | |||
} | |||
private class Hide implements Runnable { | |||
private List list; | |||
Hide(List list) { | |||
this.list = list; | |||
} | |||
@Override | |||
public void run() { | |||
while (true) { | |||
try { | |||
Thread.sleep(300); | |||
} catch (InterruptedException ex) { | |||
ex.printStackTrace(); | |||
} | |||
if (list.isSelectionEmpty()) { | |||
mediator.hideElements(true); | |||
} else { | |||
mediator.hideElements(false); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
===== components/SaveButton.java ===== | |||
<syntaxhighlight lang="java"> | |||
package refactoring_guru.mediator.example.components; | |||
import refactoring_guru.mediator.example.mediator.Mediator; | |||
import javax.swing.*; | |||
import java.awt.event.ActionEvent; | |||
/** | |||
* لا تتواصل العناصر الحقيقية مع بعضها البعض، والطريقة الوحيدة للتواصل | |||
* هي عبر إرسال الطلبات إلى الوسيط. | |||
*/ | |||
public class SaveButton extends JButton implements Component { | |||
private Mediator mediator; | |||
public SaveButton() { | |||
super("Save"); | |||
} | |||
@Override | |||
public void setMediator(Mediator mediator) { | |||
this.mediator = mediator; | |||
} | |||
@Override | |||
protected void fireActionPerformed(ActionEvent actionEvent) { | |||
mediator.saveChanges(); | |||
} | |||
@Override | |||
public String getName() { | |||
return "SaveButton"; | |||
} | |||
} | |||
</syntaxhighlight> | |||
===== components/TextBox.java ===== | |||
<syntaxhighlight lang="java"> | |||
package refactoring_guru.mediator.example.components; | |||
import refactoring_guru.mediator.example.mediator.Mediator; | |||
import javax.swing.*; | |||
import java.awt.event.KeyEvent; | |||
/** | |||
* لا تتواصل العناصر الحقيقية مع بعضها البعض، والطريقة الوحيدة للتواصل | |||
* هي عبر إرسال الطلبات إلى الوسيط. | |||
*/ | |||
public class TextBox extends JTextArea implements Component { | |||
private Mediator mediator; | |||
@Override | |||
public void setMediator(Mediator mediator) { | |||
this.mediator = mediator; | |||
} | |||
@Override | |||
protected void processComponentKeyEvent(KeyEvent keyEvent) { | |||
mediator.markNote(); | |||
} | |||
@Override | |||
public String getName() { | |||
return "TextBox"; | |||
} | |||
} | |||
</syntaxhighlight> | |||
===== components/Title.java ===== | |||
<syntaxhighlight lang="java"> | |||
package refactoring_guru.mediator.example.components; | |||
import refactoring_guru.mediator.example.mediator.Mediator; | |||
import javax.swing.*; | |||
import java.awt.event.KeyEvent; | |||
/** | |||
* لا تتواصل العناصر الحقيقية مع بعضها البعض، والطريقة الوحيدة للتواصل | |||
* هي عبر إرسال الطلبات إلى الوسيط. | |||
*/ | |||
public class Title extends JTextField implements Component { | |||
private Mediator mediator; | |||
@Override | |||
public void setMediator(Mediator mediator) { | |||
this.mediator = mediator; | |||
} | |||
@Override | |||
protected void processComponentKeyEvent(KeyEvent keyEvent) { | |||
mediator.markNote(); | |||
} | |||
@Override | |||
public String getName() { | |||
return "Title"; | |||
} | |||
} | |||
</syntaxhighlight> | |||
==== الوسيط ==== | |||
===== mediator/Mediator.java: يحدد واجهة الوسيط المشتركة ===== | |||
<syntaxhighlight lang="java"> | |||
package refactoring_guru.mediator.example.mediator; | |||
import refactoring_guru.mediator.example.components.Component; | |||
import javax.swing.*; | |||
/** | |||
* واجهة الوسيط المشتركة. | |||
*/ | |||
public interface Mediator { | |||
void addNewNote(Note note); | |||
void deleteNote(); | |||
void getInfoFromList(Note note); | |||
void saveChanges(); | |||
void markNote(); | |||
void clear(); | |||
void sendToFilter(ListModel listModel); | |||
void setElementsList(ListModel list); | |||
void registerComponent(Component component); | |||
void hideElements(boolean flag); | |||
void createGUI(); | |||
} | |||
</syntaxhighlight> | |||
===== mediator/Editor.java: الوسيط الحقيقي ===== | |||
<syntaxhighlight lang="java"> | |||
package refactoring_guru.mediator.example.mediator; | |||
import refactoring_guru.mediator.example.components.*; | |||
import refactoring_guru.mediator.example.components.Component; | |||
import refactoring_guru.mediator.example.components.List; | |||
import javax.swing.*; | |||
import javax.swing.border.LineBorder; | |||
import java.awt.*; | |||
/** | |||
* الوسيط الحقيقي، استُخرِجت كل عمليات التواصل العشوائية بين العناصر الحقيقية | |||
* إلى الوسيط، والآن تتواصل العناصر مع الوسيط الذي يعرف كيف يعالج الطلبات. | |||
*/ | |||
public class Editor implements Mediator { | |||
private Title title; | |||
private TextBox textBox; | |||
private AddButton add; | |||
private DeleteButton del; | |||
private SaveButton save; | |||
private List list; | |||
private Filter filter; | |||
private JLabel titleLabel = new JLabel("Title:"); | |||
private JLabel textLabel = new JLabel("Text:"); | |||
private JLabel label = new JLabel("Add or select existing note to proceed..."); | |||
/** | |||
* هنا تسجيل العناصر من خلال الوسيط. | |||
*/ | |||
@Override | |||
public void registerComponent(Component component) { | |||
component.setMediator(this); | |||
switch (component.getName()) { | |||
case "AddButton": | |||
add = (AddButton)component; | |||
break; | |||
case "DelButton": | |||
del = (DeleteButton)component; | |||
break; | |||
case "Filter": | |||
filter = (Filter)component; | |||
break; | |||
case "List": | |||
list = (List)component; | |||
this.list.addListSelectionListener(listSelectionEvent -> { | |||
Note note = (Note)list.getSelectedValue(); | |||
if (note != null) { | |||
getInfoFromList(note); | |||
} else { | |||
clear(); | |||
} | |||
}); | |||
break; | |||
case "SaveButton": | |||
save = (SaveButton)component; | |||
break; | |||
case "TextBox": | |||
textBox = (TextBox)component; | |||
break; | |||
case "Title": | |||
title = (Title)component; | |||
break; | |||
} | |||
} | |||
/** | |||
* أساليب متعددة لمعالجة الطلبات من عناصر بعينها. | |||
*/ | |||
@Override | |||
public void addNewNote(Note note) { | |||
title.setText(""); | |||
textBox.setText(""); | |||
list.addElement(note); | |||
} | |||
@Override | |||
public void deleteNote() { | |||
list.deleteElement(); | |||
} | |||
@Override | |||
public void getInfoFromList(Note note) { | |||
title.setText(note.getName().replace('*', ' ')); | |||
textBox.setText(note.getText()); | |||
} | |||
@Override | |||
public void saveChanges() { | |||
try { | |||
Note note = (Note) list.getSelectedValue(); | |||
note.setName(title.getText()); | |||
note.setText(textBox.getText()); | |||
list.repaint(); | |||
} catch (NullPointerException ignored) {} | |||
} | |||
@Override | |||
public void markNote() { | |||
try { | |||
Note note = list.getCurrentElement(); | |||
String name = note.getName(); | |||
if (!name.endsWith("*")) { | |||
note.setName(note.getName() + "*"); | |||
} | |||
list.repaint(); | |||
} catch (NullPointerException ignored) {} | |||
} | |||
@Override | |||
public void clear() { | |||
title.setText(""); | |||
textBox.setText(""); | |||
} | |||
@Override | |||
public void sendToFilter(ListModel listModel) { | |||
filter.setList(listModel); | |||
} | |||
@SuppressWarnings("unchecked") | |||
@Override | |||
public void setElementsList(ListModel list) { | |||
this.list.setModel(list); | |||
this.list.repaint(); | |||
} | |||
@Override | |||
public void hideElements(boolean flag) { | |||
titleLabel.setVisible(!flag); | |||
textLabel.setVisible(!flag); | |||
title.setVisible(!flag); | |||
textBox.setVisible(!flag); | |||
save.setVisible(!flag); | |||
label.setVisible(flag); | |||
} | |||
@Override | |||
public void createGUI() { | |||
JFrame notes = new JFrame("Notes"); | |||
notes.setSize(960, 600); | |||
notes.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); | |||
JPanel left = new JPanel(); | |||
left.setBorder(new LineBorder(Color.BLACK)); | |||
left.setSize(320, 600); | |||
left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS)); | |||
JPanel filterPanel = new JPanel(); | |||
filterPanel.add(new JLabel("Filter:")); | |||
filter.setColumns(20); | |||
filterPanel.add(filter); | |||
filterPanel.setPreferredSize(new Dimension(280, 40)); | |||
JPanel listPanel = new JPanel(); | |||
list.setFixedCellWidth(260); | |||
listPanel.setSize(320, 470); | |||
JScrollPane scrollPane = new JScrollPane(list); | |||
scrollPane.setPreferredSize(new Dimension(275, 410)); | |||
listPanel.add(scrollPane); | |||
JPanel buttonPanel = new JPanel(); | |||
add.setPreferredSize(new Dimension(85, 25)); | |||
buttonPanel.add(add); | |||
del.setPreferredSize(new Dimension(85, 25)); | |||
buttonPanel.add(del); | |||
buttonPanel.setLayout(new FlowLayout()); | |||
left.add(filterPanel); | |||
left.add(listPanel); | |||
left.add(buttonPanel); | |||
JPanel right = new JPanel(); | |||
right.setLayout(null); | |||
right.setSize(640, 600); | |||
right.setLocation(320, 0); | |||
right.setBorder(new LineBorder(Color.BLACK)); | |||
titleLabel.setBounds(20, 4, 50, 20); | |||
title.setBounds(60, 5, 555, 20); | |||
textLabel.setBounds(20, 4, 50, 130); | |||
textBox.setBorder(new LineBorder(Color.DARK_GRAY)); | |||
textBox.setBounds(20, 80, 595, 410); | |||
save.setBounds(270, 535, 80, 25); | |||
label.setFont(new Font("Verdana", Font.PLAIN, 22)); | |||
label.setBounds(100, 240, 500, 100); | |||
right.add(label); | |||
right.add(titleLabel); | |||
right.add(title); | |||
right.add(textLabel); | |||
right.add(textBox); | |||
right.add(save); | |||
notes.setLayout(null); | |||
notes.getContentPane().add(left); | |||
notes.getContentPane().add(right); | |||
notes.setResizable(false); | |||
notes.setLocationRelativeTo(null); | |||
notes.setVisible(true); | |||
} | |||
} | |||
</syntaxhighlight> | |||
===== mediator/Note.java: فئة المذكرة ===== | |||
<syntaxhighlight lang="java"> | |||
package refactoring_guru.mediator.example.mediator; | |||
/** | |||
* فئة المذكرة. | |||
*/ | |||
public class Note { | |||
private String name; | |||
private String text; | |||
public Note() { | |||
name = "New note"; | |||
} | |||
public void setName(String name) { | |||
this.name = name; | |||
} | |||
public void setText(String text) { | |||
this.text = text; | |||
} | |||
public String getName() { | |||
return name; | |||
} | |||
public String getText() { | |||
return text; | |||
} | |||
@Override | |||
public String toString() { | |||
return name; | |||
} | |||
} | |||
</syntaxhighlight> | |||
===== Demo.java: شيفرة البدء (Initialization Code) ===== | |||
<syntaxhighlight lang="java"> | |||
package refactoring_guru.mediator.example; | |||
import refactoring_guru.mediator.example.components.*; | |||
import refactoring_guru.mediator.example.mediator.Editor; | |||
import refactoring_guru.mediator.example.mediator.Mediator; | |||
import javax.swing.*; | |||
/** | |||
* فئة العرض، يجتمع كل شيء ها هنا. | |||
*/ | |||
public class Demo { | |||
public static void main(String[] args) { | |||
Mediator mediator = new Editor(); | |||
mediator.registerComponent(new Title()); | |||
mediator.registerComponent(new TextBox()); | |||
mediator.registerComponent(new AddButton()); | |||
mediator.registerComponent(new DeleteButton()); | |||
mediator.registerComponent(new SaveButton()); | |||
mediator.registerComponent(new List(new DefaultListModel())); | |||
mediator.registerComponent(new Filter()); | |||
mediator.createGUI(); | |||
} | |||
} | |||
</syntaxhighlight> | |||
===== OutputDemo.png: نتائج التنفيذ ===== | |||
[[ملف:dpm.OutputDemo.png|بدون|تصغير|(ش.7)]] | |||
== الاستخدام في لغة #C == | |||
'''المستوى:''' ★ ★ ☆ | |||
'''الانتشار:''' ★ ★ ☆ | |||
'''أمثلة الاستخدام:''' لعل تسهيل التواصل بين عناصر الواجهة الرسومية لبرنامج ما هو أكثر مثال منتشر لنمط الوسيط في لغة #C. ويكون مرادف الوسيط هو الجزء المتحكم في نمط MVC. | |||
=== مثال تصوري === | |||
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية: | |||
* ما الفئات التي يتكون منها؟ | |||
* ما الأدوار التي تلعبها هذه الفئات؟ | |||
* كيف ترتبط عناصر النمط ببعضها؟ | |||
==== Program.cs: مثال تصوري ==== | |||
<syntaxhighlight lang="c#"> | |||
using System; | |||
namespace RefactoringGuru.DesignPatterns.Mediator.Conceptual | |||
{ | |||
// تصرِّح واجهة الوسيط عن أسلوب تستخدمه العناصر لإشعار الوسيط | |||
// بالأحداث المختلفة، وقد يتفاعل الوسيط مع هذه الأحداث ويمرر التنفيذ إلى | |||
// عناصر أخرى. | |||
public interface IMediator | |||
{ | |||
void Notify(object sender, string ev); | |||
} | |||
// سلوكًا تعاونيًا من خلال mediators يستخدم الوسطاء | |||
// التنسيق بين عدة عناصر. | |||
class ConcreteMediator : IMediator | |||
{ | |||
private Component1 _component1; | |||
private Component2 _component2; | |||
public ConcreteMediator(Component1 component1, Component2 component2) | |||
{ | |||
this._component1 = component1; | |||
this._component1.SetMediator(this); | |||
this._component2 = component2; | |||
this._component2.SetMediator(this); | |||
} | |||
public void Notify(object sender, string ev) | |||
{ | |||
if (ev == "A") | |||
{ | |||
Console.WriteLine("Mediator reacts on A and triggers folowing operations:"); | |||
this._component2.DoC(); | |||
} | |||
if (ev == "D") | |||
{ | |||
Console.WriteLine("Mediator reacts on D and triggers following operations:"); | |||
this._component1.DoB(); | |||
this._component2.DoC(); | |||
} | |||
} | |||
} | |||
// الوظيفية الأساسية لتخزين نسخة من الوسيط Base Component يوفر العنصر الأساسي | |||
// داخل كائنات العنصر. | |||
class BaseComponent | |||
{ | |||
protected IMediator _mediator; | |||
public BaseComponent(IMediator mediator = null) | |||
{ | |||
this._mediator = mediator; | |||
} | |||
public void SetMediator(IMediator mediator) | |||
{ | |||
this._mediator = mediator; | |||
} | |||
} | |||
// متعددة functionalities تستخدم العناصر الحقيقية وظيفيات. | |||
// ولا تعتمد هذه العناصر على عناصر غيرها ولا على أي فئة وسيط حقيقية. | |||
class Component1 : BaseComponent | |||
{ | |||
public void DoA() | |||
{ | |||
Console.WriteLine("Component 1 does A."); | |||
this._mediator.Notify(this, "A"); | |||
} | |||
public void DoB() | |||
{ | |||
Console.WriteLine("Component 1 does B."); | |||
this._mediator.Notify(this, "B"); | |||
} | |||
} | |||
class Component2 : BaseComponent | |||
{ | |||
public void DoC() | |||
{ | |||
Console.WriteLine("Component 2 does C."); | |||
this._mediator.Notify(this, "C"); | |||
} | |||
public void DoD() | |||
{ | |||
Console.WriteLine("Component 2 does D."); | |||
this._mediator.Notify(this, "D"); | |||
} | |||
} | |||
class Program | |||
{ | |||
static void Main(string[] args) | |||
{ | |||
// شيفرة العميل. | |||
Component1 component1 = new Component1(); | |||
Component2 component2 = new Component2(); | |||
new ConcreteMediator(component1, component2); | |||
Console.WriteLine("Client triggets operation A."); | |||
component1.DoA(); | |||
Console.WriteLine(); | |||
Console.WriteLine("Client triggers operation D."); | |||
component2.DoD(); | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
===== Output.txt: نتائج التنفيذ ===== | |||
<syntaxhighlight lang="text"> | |||
Client triggers operation A. | |||
Component 1 does A. | |||
Mediator reacts on A and triggers following operations: | |||
Component 2 does C. | |||
Client triggers operation D. | |||
Component 2 does D. | |||
Mediator reacts on D and triggers following operations: | |||
Component 1 does B. | |||
Component 2 does C. | |||
</syntaxhighlight> | |||
== الاستخدام في لغة PHP == | |||
'''المستوى:''' ★ ★ ☆ | |||
'''الانتشار:''' ★ ★ ☆ | |||
أمثلة الاستخدام: لا يكثر استخدام نمط الوسيط بشكل خالص في لغة [[PHP]] على خلاف اللغات الأخرى، خاصة تلك التي تستهدف الواجهات الرسومية مثل جافا أو #C، فقد يحتوي تطبيق PHP على عشرات العناصر، لكنها نادرًا ما تتواصل مباشرة داخل جلسة واحدة. | |||
لكن رغم هذا، لا تزال لدينا استخدامات لنمط الوسيط مثل مرسِلات الأحداث (event dispatchers) للعديد من أطر العمل في PHP، أو بعض التطبيقات لمتحكمات MVC. | |||
=== مثال تصوري === | |||
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية: | |||
* ما الفئات التي يتكون منها؟ | |||
* ما الأدوار التي تلعبها هذه الفئات؟ | |||
* كيف ترتبط عناصر النمط ببعضها؟ | |||
سيكون من السهل عليك استيعاب المثال التالي بعد تعلم بنية النمط، بناء على استخدام واقعي له في لغة PHP. | |||
==== index.php: مثال تصوري ==== | |||
<syntaxhighlight lang="php"> | |||
<?php | |||
namespace RefactoringGuru\Mediator\Conceptual; | |||
/** | |||
* تصرِّح واجهة الوسيط عن أسلوب تستخدمه العناصر لإشعار الوسيط | |||
* بالأحداث المختلفة، وقد يتفاعل الوسيط مع هذه الأحداث ويمرر التنفيذ إلى | |||
* عناصر أخرى. | |||
*/ | |||
interface Mediator | |||
{ | |||
public function notify(object $sender, string $event): void; | |||
} | |||
/** | |||
* سلوكًا تعاونيًا من خلال mediators يستخدم الوسطاء | |||
* التنسيق بين عدة عناصر. | |||
*/ | |||
class ConcreteMediator implements Mediator | |||
{ | |||
private $component1; | |||
private $component2; | |||
public function __construct(Component1 $c1, Component2 $c2) | |||
{ | |||
$this->component1 = $c1; | |||
$this->component1->setMediator($this); | |||
$this->component2 = $c2; | |||
$this->component2->setMediator($this); | |||
} | |||
public function notify(object $sender, string $event): void | |||
{ | |||
if ($event == "A") { | |||
echo "Mediator reacts on A and triggers following operations:\n"; | |||
$this->component2->doC(); | |||
} | |||
if ($event == "D") { | |||
echo "Mediator reacts on D and triggers following operations:\n"; | |||
$this->component1->doB(); | |||
$this->component2->doC(); | |||
} | |||
} | |||
} | |||
/** | |||
* الوظيفية الأساسية لتخزين نسخة من الوسيط Base Component يوفر العنصر الأساسي | |||
* داخل كائنات العنصر. | |||
*/ | |||
class BaseComponent | |||
{ | |||
protected $mediator; | |||
public function __construct(Mediator $mediator = null) | |||
{ | |||
$this->mediator = $mediator; | |||
} | |||
public function setMediator(Mediator $mediator): void | |||
{ | |||
$this->mediator = $mediator; | |||
} | |||
} | |||
/** | |||
* متعددة functionalities تستخدم العناصر الحقيقية وظيفيات. | |||
* ولا تعتمد هذه العناصر على عناصر غيرها ولا على أي فئة وسيط حقيقية. | |||
*/ | |||
class Component1 extends BaseComponent | |||
{ | |||
public function doA(): void | |||
{ | |||
echo "Component 1 does A.\n"; | |||
$this->mediator->notify($this, "A"); | |||
} | |||
public function doB(): void | |||
{ | |||
echo "Component 1 does B.\n"; | |||
$this->mediator->notify($this, "B"); | |||
} | |||
} | |||
class Component2 extends BaseComponent | |||
{ | |||
public function doC(): void | |||
{ | |||
echo "Component 2 does C.\n"; | |||
$this->mediator->notify($this, "C"); | |||
} | |||
public function doD(): void | |||
{ | |||
echo "Component 2 does D.\n"; | |||
$this->mediator->notify($this, "D"); | |||
} | |||
} | |||
/** | |||
* شيفرة العميل | |||
*/ | |||
$c1 = new Component1; | |||
$c2 = new Component2; | |||
$mediator = new ConcreteMediator($c1, $c2); | |||
echo "Client triggers operation A.\n"; | |||
$c1->doA(); | |||
echo "\n"; | |||
echo "Client triggers operation D.\n"; | |||
$c2->doD(); | |||
</syntaxhighlight> | |||
===== Output.txt: نتائج التنفيذ ===== | |||
<syntaxhighlight lang="text"> | |||
Client triggers operation A. | |||
Component 1 does A. | |||
Mediator reacts on A and triggers following operations: | |||
Component 2 does C. | |||
Client triggers operation D. | |||
Component 2 does D. | |||
Mediator reacts on D and triggers following operations: | |||
Component 1 does B. | |||
Component 2 does C. | |||
</syntaxhighlight> | |||
=== مثال واقعي === | |||
في هذا المثال، يوضح نمط الوسيط فكرة نمط المراقِب (Observer) من خلال توفير مرسل أحداث مركزي، سامحًا لأي كائن أن يتعقب ويبدأ الأحداث في كائنات أخرى دون الاعتماد على فئاتها. | |||
==== index.php: مثال واقعي ==== | |||
<syntaxhighlight lang="php"> | |||
<?php | |||
namespace RefactoringGuru\Mediator\RealWorld; | |||
/** | |||
* تتصرف فئة مرسل الحدث كوسيط وتحتوي منطق الاشتراك والإشعار، | |||
* وترتبط بواجهاتها المجردة فقط | |||
* على الرغم من اعتماد الوسيط التقليدي في العادة على فئات العنصر الحقيقية. | |||
* | |||
* ونحن نستطيع تحقيق هذا المستوى من المراوغة بفضل الطريقة التي تُنشأ بها | |||
* الاتصالات بين العناصر، ذلك أن العناصر نفسها قد تشترك في | |||
* أحداث معينة تريدها من خلال واجهة الاشتراك للوسيط. | |||
* | |||
* PHP المضمنة في لغة Subject/Observer لاحظ أننا لا نستطيع استخدام واجهات | |||
* لأننا بذلك سنكون حرفنا هذه الواجهات بعيدًا جدًا عن الغرض المصممة له. | |||
*/ | |||
class EventDispatcher | |||
{ | |||
/** | |||
* @var array | |||
*/ | |||
private $observers = []; | |||
public function __construct() | |||
// مجموعة الأحداث الخاصة للمراقبين الذين يريدون الاستماع للأحداث كلها. | |||
$this->observers["*"] = []; | |||
} | |||
private function initEventGroup(string &$event = "*"): void | |||
{ | |||
if (!isset($this->observers[$event])) { | |||
$this->observers[$event] = []; | |||
} | |||
} | |||
private function getEventObservers(string $event = "*"): array | |||
{ | |||
$this->initEventGroup($event); | |||
$group = $this->observers[$event]; | |||
$all = $this->observers["*"]; | |||
return array_merge($group, $all); | |||
} | |||
public function attach(Observer $observer, string $event = "*"): void | |||
{ | |||
$this->initEventGroup($event); | |||
$this->observers[$event][] = $observer; | |||
} | |||
public function detach(Observer $observer, string $event = "*"): void | |||
{ | |||
foreach ($this->getEventObservers($event) as $key => $s) { | |||
if ($s === $observer) { | |||
unset($this->observers[$event][$key]); | |||
} | |||
} | |||
} | |||
public function trigger(string $event, object $emitter, $data = null): void | |||
{ | |||
echo "EventDispatcher: Broadcasting the '$event' event.\n"; | |||
foreach ($this->getEventObservers($event) as $observer) { | |||
$observer->update($event, $emitter, $data); | |||
} | |||
} | |||
} | |||
/** | |||
* دالة مساعد بسيطة لتوفير وصول عام لمرسل الأحداث. | |||
*/ | |||
function events(): EventDispatcher | |||
{ | |||
static $eventDispatcher; | |||
if (!$eventDispatcher) { | |||
$eventDispatcher = new EventDispatcher; | |||
} | |||
return $eventDispatcher; | |||
} | |||
/** | |||
* تحدد واجهة المراقب كيف تستقبل العناصر إشعارات الحدث. | |||
*/ | |||
interface Observer | |||
{ | |||
public function update(string $event, object $emitter, $data = null); | |||
} | |||
/** | |||
* يتصرف كعنصر عادي UserRepository على عكس مثال نمط المراقب، فإن هذا المثال يجعل | |||
* لا يملك أي أساليب تتعلق بالأحداث. وتعتمد هذه الفئة | |||
* من أجل بث أحداثها والاستماع إلى أحداث EventDispatcher كأي عنصر آخر على | |||
* غيرها من الفئات. | |||
* @see \RefactoringGuru\Observer\RealWorld\UserRepository | |||
*/ | |||
class UserRepository implements Observer | |||
{ | |||
/** | |||
* @var array قائمة بمستخدمي التطبيق. | |||
*/ | |||
private $users = []; | |||
/** | |||
* بإمكان العناصر أن تشترك في الأحداث بنفسها أو من خلال شيفرة العميل. | |||
*/ | |||
public function __construct() | |||
{ | |||
events()->attach($this, "users:deleted"); | |||
} | |||
/** | |||
* تستطيع العناصر أن تقرر ما إن كانت تريد معالجة الحدث مستخدمة اسمه | |||
* أو أي بيانات أخرى مُرِّرَت مع الحدث emitter أو المرسل. | |||
*/ | |||
public function update(string $event, object $emitter, $data = null): void | |||
{ | |||
switch ($event) { | |||
case "users:deleted": | |||
if ($emitter === $this) { | |||
return; | |||
} | |||
$this->deleteUser($data, true); | |||
break; | |||
} | |||
} | |||
// تمثل هذه الأساليب منطق العمل الفئة. | |||
public function initialize(string $filename): void | |||
{ | |||
echo "UserRepository: Loading user records from a file.\n"; | |||
// ... | |||
events()->trigger("users:init", $this, $filename); | |||
} | |||
public function createUser(array $data, bool $silent = false): User | |||
{ | |||
echo "UserRepository: Creating a user.\n"; | |||
$user = new User; | |||
$user->update($data); | |||
$id = bin2hex(openssl_random_pseudo_bytes(16)); | |||
$user->update(["id" => $id]); | |||
$this->users[$id] = $user; | |||
if (!$silent) { | |||
events()->trigger("users:created", $this, $user); | |||
} | |||
return $user; | |||
} | |||
public function updateUser(User $user, array $data, bool $silent = false): User | |||
{ | |||
echo "UserRepository: Updating a user.\n"; | |||
$id = $user->attributes["id"]; | |||
if (!isset($this->users[$id])) { | |||
return null; | |||
} | |||
$user = $this->users[$id]; | |||
$user->update($data); | |||
if (!$silent) { | |||
events()->trigger("users:updated", $this, $user); | |||
} | |||
return $user; | |||
} | |||
public function deleteUser(User $user, bool $silent = false): void | |||
{ | |||
echo "UserRepository: Deleting a user.\n"; | |||
$id = $user->attributes["id"]; | |||
if (!isset($this->users[$id])) { | |||
return; | |||
} | |||
unset($this->users[$id]); | |||
if (!$silent) { | |||
events()->trigger("users:deleted", $this, $user); | |||
} | |||
} | |||
} | |||
/** | |||
* سنجعل فئة المستخدم مهمشة وغير مهمة بما أنها ليست الهدف من مثالنا. | |||
*/ | |||
class User | |||
{ | |||
public $attributes = []; | |||
public function update($data): void | |||
{ | |||
$this->attributes = array_merge($this->attributes, $data); | |||
} | |||
/** | |||
* تستطيع كل الكائنات أن تبدأ أحداثًا. | |||
*/ | |||
public function delete(): void | |||
{ | |||
echo "User: I can now delete myself without worrying about the repository.\n"; | |||
events()->trigger("users:deleted", $this, $this); | |||
} | |||
} | |||
/** | |||
* يسجل هذا العنصر الحقيقي أي أحداث يكون مشتركًا فيها. | |||
*/ | |||
class Logger implements Observer | |||
{ | |||
private $filename; | |||
public function __construct($filename) | |||
{ | |||
$this->filename = $filename; | |||
if (file_exists($this->filename)) { | |||
unlink($this->filename); | |||
} | |||
} | |||
public function update(string $event, object $emitter, $data = null) | |||
{ | |||
$entry = date("Y-m-d H:i:s") . ": '$event' with data '" . json_encode($data) . "'\n"; | |||
file_put_contents($this->filename, $entry, FILE_APPEND); | |||
echo "Logger: I've written '$event' entry to the log.\n"; | |||
} | |||
} | |||
/** | |||
* يرسل هذا العنصر الحقيقي إرشادات مبدئية للمستخدمين الجدد، ويكون العميل | |||
* مسؤولًا عن إرفاق هذا العنصر في حدث إنشاء مستخدم مناسب. | |||
*/ | |||
class OnboardingNotification implements Observer | |||
{ | |||
private $adminEmail; | |||
public function __construct(string $adminEmail) | |||
{ | |||
$this->adminEmail = $adminEmail; | |||
} | |||
public function update(string $event, object $emitter, $data = null): void | |||
{ | |||
// mail($this->adminEmail, | |||
// "Onboarding required", | |||
// "We have a new user. Here's his info: " .json_encode($data)); | |||
echo "OnboardingNotification: The notification has been emailed!\n"; | |||
} | |||
} | |||
/** | |||
* شيفرة العميل. | |||
*/ | |||
$repository = new UserRepository; | |||
events()->attach($repository, "facebook:update"); | |||
$logger = new Logger(__DIR__ . "/log.txt"); | |||
events()->attach($logger, "*"); | |||
$onboarding = new OnboardingNotification("1@example.com"); | |||
events()->attach($onboarding, "users:created"); | |||
// ... | |||
$repository->initialize(__DIR__ . "users.csv"); | |||
// ... | |||
$user = $repository->createUser([ | |||
"name" => "John Smith", | |||
"email" => "john99@example.com", | |||
]); | |||
// ... | |||
$user->delete(); | |||
</syntaxhighlight> | |||
==== Output.txt: نتائج التنفيذ ==== | |||
<syntaxhighlight lang="text"> | |||
UserRepository: Loading user records from a file. | |||
EventDispatcher: Broadcasting the 'users:init' event. | |||
Logger: I've written 'users:init' entry to the log. | |||
UserRepository: Creating a user. | |||
EventDispatcher: Broadcasting the 'users:created' event. | |||
OnboardingNotification: The notification has been emailed! | |||
Logger: I've written 'users:created' entry to the log. | |||
User: I can now delete myself without worrying about the repository. | |||
EventDispatcher: Broadcasting the 'users:deleted' event. | |||
UserRepository: Deleting a user. | |||
Logger: I've written 'users:deleted' entry to the log. | |||
</syntaxhighlight> | |||
== الاستخدام في لغة بايثون == | |||
'''المستوى:''' ★ ★ ☆ | |||
'''الانتشار:''' ☆ ☆ ☆ | |||
'''أمثلة الاستخدام:''' لعل تسهيل التواصل بين عناصر الواجهة الرسومية لبرنامج ما هو أكثر مثال منتشر لنمط الوسيط في لغة بايثون، ويكون مرادف الوسيط هو الجزء المتحكم في نمط MVC. | |||
=== مثال تصوري === | |||
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية: | |||
* ما الفئات التي يتكون منها؟ | |||
* ما الأدوار التي تلعبها هذه الفئات؟ | |||
* كيف ترتبط عناصر النمط ببعضها؟ | |||
==== main.py: مثال تصوري ==== | |||
<syntaxhighlight lang="python"> | |||
from __future__ import annotations | |||
from abc import ABC | |||
class Mediator(ABC): | |||
""" | |||
تصرِّح واجهة الوسيط عن أسلوب تستخدمه العناصر لإشعار الوسيط | |||
بالأحداث المختلفة، وقد يتفاعل الوسيط مع هذه الأحداث ويمرر التنفيذ إلى | |||
عناصر أخرى. | |||
""" | |||
def notify(self, sender: object, event: str) -> None: | |||
pass | |||
class ConcreteMediator(Mediator): | |||
def __init__(self, component1: Component1, component2: Component2) -> None: | |||
self._component1 = component1 | |||
self._component1.mediator = self | |||
self._component2 = component2 | |||
self._component2.mediator = self | |||
def notify(self, sender: object, event: str) -> None: | |||
if event == "A": | |||
print("Mediator reacts on A and triggers following operations:") | |||
self._component2.do_c() | |||
elif event == "D": | |||
print("Mediator reacts on D and triggers following operations:") | |||
self._component1.do_b() | |||
self._component2.do_c() | |||
class BaseComponent: | |||
""" | |||
الوظيفية الأساسية لتخزين نسخة من الوسيط Base Component يوفر العنصر الأساسي | |||
داخل كائنات العنصر. | |||
""" | |||
def __init__(self, mediator: Mediator = None) -> None: | |||
self._mediator = mediator | |||
@property | |||
def mediator(self) -> Mediator: | |||
return self._mediator | |||
@mediator.setter | |||
def mediator(self, mediator: Mediator) -> None: | |||
self._mediator = mediator | |||
""" | |||
متعددة functionalities تستخدم العناصر الحقيقية وظيفيات. | |||
ولا تعتمد هذه العناصر على عناصر غيرها ولا على أي فئة وسيط حقيقية. | |||
""" | |||
class Component1(BaseComponent): | |||
def do_a(self) -> None: | |||
print("Component 1 does A.") | |||
self.mediator.notify(self, "A") | |||
def do_b(self) -> None: | |||
print("Component 1 does B.") | |||
self.mediator.notify(self, "B") | |||
class Component2(BaseComponent): | |||
def do_c(self) -> None: | |||
print("Component 2 does C.") | |||
self.mediator.notify(self, "C") | |||
def do_d(self) -> None: | |||
print("Component 2 does D.") | |||
self.mediator.notify(self, "D") | |||
if __name__ == "__main__": | |||
# The client code. | |||
c1 = Component1() | |||
c2 = Component2() | |||
mediator = ConcreteMediator(c1, c2) | |||
print("Client triggers operation A.") | |||
c1.do_a() | |||
print("\n", end="") | |||
print("Client triggers operation D.") | |||
c2.do_d() | |||
</syntaxhighlight> | |||
==== Output.txt: نتائج التنفيذ ==== | |||
<syntaxhighlight lang="text"> | |||
Client triggers operation A. | |||
Component 1 does A. | |||
Mediator reacts on A and triggers following operations: | |||
Component 2 does C. | |||
Client triggers operation D. | |||
Component 2 does D. | |||
Mediator reacts on D and triggers following operations: | |||
Component 1 does B. | |||
Component 2 does C. | |||
</syntaxhighlight> | |||
== الاستخدام في لغة روبي == | |||
'''المستوى:''' ★ ★ ☆ | |||
'''الانتشار:''' ☆ ☆ ☆ | |||
'''أمثلة الاستخدام:''' لعل تسهيل التواصل بين عناصر الواجهة الرسومية لبرنامج ما هو أكثر مثال منتشر لنمط الوسيط في لغة روبي، ويكون مرادف الوسيط هو الجزء المتحكم في نمط MVC. | |||
=== مثال تصوري === | |||
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية: | |||
* ما الفئات التي يتكون منها؟ | |||
* ما الأدوار التي تلعبها هذه الفئات؟ | |||
* كيف ترتبط عناصر النمط ببعضها؟ | |||
==== main.rb: مثال تصوري ==== | |||
<syntaxhighlight lang="ruby"> | |||
# تصرِّح واجهة الوسيط عن أسلوب تستخدمه العناصر لإشعار الوسيط | |||
# بالأحداث المختلفة، وقد يتفاعل الوسيط مع هذه الأحداث ويمرر التنفيذ إلى | |||
# عناصر أخرى. | |||
class Mediator | |||
# @abstract | |||
# | |||
# @param [Object] sender | |||
# @param [String] event | |||
def notify(_sender, _event) | |||
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" | |||
end | |||
end | |||
class ConcreteMediator < Mediator | |||
# @param [Component1] component1 | |||
# @param [Component2] component2 | |||
def initialize(component1, component2) | |||
@component1 = component1 | |||
@component1.mediator = self | |||
@component2 = component2 | |||
@component2.mediator = self | |||
end | |||
# @param [Object] sender | |||
# @param [String] event | |||
def notify(_sender, event) | |||
if event == 'A' | |||
puts 'Mediator reacts on A and triggers following operations:' | |||
@component2.do_c | |||
elsif event == 'D' | |||
puts 'Mediator reacts on D and triggers following operations:' | |||
@component1.do_b | |||
@component2.do_c | |||
end | |||
end | |||
end | |||
# الوظيفية الأساسية لتخزين نسخة من الوسيط Base Component يوفر العنصر الأساسي | |||
# داخل كائنات العنصر. | |||
class BaseComponent | |||
# @return [Mediator] | |||
attr_accessor :mediator | |||
# @param [Mediator] mediator | |||
def initialize(mediator = nil) | |||
@mediator = mediator | |||
end | |||
end | |||
# متعددة functionalities تستخدم العناصر الحقيقية وظيفيات. | |||
# ولا تعتمد هذه العناصر على عناصر غيرها ولا على أي فئة وسيط حقيقية. | |||
class Component1 < BaseComponent | |||
def do_a | |||
puts 'Component 1 does A.' | |||
@mediator.notify(self, 'A') | |||
end | |||
def do_b | |||
puts 'Component 1 does B.' | |||
@mediator.notify(self, 'B') | |||
end | |||
end | |||
class Component2 < BaseComponent | |||
def do_c | |||
puts 'Component 2 does C.' | |||
@mediator.notify(self, 'C') | |||
end | |||
def do_d | |||
puts 'Component 2 does D.' | |||
@mediator.notify(self, 'D') | |||
end | |||
end | |||
# شيفرة العميل. | |||
c1 = Component1.new | |||
c2 = Component2.new | |||
ConcreteMediator.new(c1, c2) | |||
puts 'Client triggers operation A.' | |||
c1.do_a | |||
puts "\n" | |||
puts 'Client triggers operation D.' | |||
c2.do_d | |||
</syntaxhighlight> | |||
==== output.txt: نتائج التنفيذ ==== | |||
<syntaxhighlight lang="text"> | |||
Client triggers operation A. | |||
Component 1 does A. | |||
Mediator reacts on A and triggers following operations: | |||
Component 2 does C. | |||
Client triggers operation D. | |||
Component 2 does D. | |||
Mediator reacts on D and triggers following operations: | |||
Component 1 does B. | |||
Component 2 does C. | |||
</syntaxhighlight> | |||
== الاستخدام في لغة Swift == | |||
'''المستوى:''' ★ ★ ☆ | |||
'''الانتشار:''' ☆ ☆ ☆ | |||
'''أمثلة الاستخدام:''' لعل تسهيل التواصل بين عناصر الواجهة الرسومية لبرنامج ما هو أكثر مثال منتشر لنمط الوسيط في لغة Swift. ويكون مرادف الوسيط هو الجزء المتحكم في نمط MVC. | |||
=== مثال تصوري === | |||
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية: | |||
* ما الفئات التي يتكون منها؟ | |||
* ما الأدوار التي تلعبها هذه الفئات؟ | |||
* كيف ترتبط عناصر النمط ببعضها؟ | |||
==== Example.swift: مثال تصوري ==== | |||
<syntaxhighlight lang="swift"> | |||
import XCTest | |||
/// تصرِّح واجهة الوسيط عن أسلوب تستخدمه العناصر لإشعار الوسيط | |||
/// بالأحداث المختلفة، وقد يتفاعل الوسيط مع هذه الأحداث ويمرر التنفيذ إلى | |||
/// عناصر أخرى. | |||
protocol Mediator: AnyObject { | |||
func notify(sender: BaseComponent, event: String) | |||
} | |||
/// سلوكًا تعاونيًا من خلال mediators يستخدم الوسطاء | |||
/// التنسيق بين عدة عناصر. | |||
class ConcreteMediator: Mediator { | |||
private var component1: Component1 | |||
private var component2: Component2 | |||
init(_ component1: Component1, _ component2: Component2) { | |||
self.component1 = component1 | |||
self.component2 = component2 | |||
component1.update(mediator: self) | |||
component2.update(mediator: self) | |||
} | |||
func notify(sender: BaseComponent, event: String) { | |||
if event == "A" { | |||
print("Mediator reacts on A and triggers following operations:") | |||
self.component2.doC() | |||
} | |||
else if (event == "D") { | |||
print("Mediator reacts on D and triggers following operations:") | |||
self.component1.doB() | |||
self.component2.doC() | |||
} | |||
} | |||
} | |||
/// الوظيفية الأساسية لتخزين نسخة من الوسيط Base Component يوفر العنصر الأساسي | |||
/// داخل كائنات العنصر. | |||
class BaseComponent { | |||
fileprivate weak var mediator: Mediator? | |||
init(mediator: Mediator? = nil) { | |||
self.mediator = mediator | |||
} | |||
func update(mediator: Mediator) { | |||
self.mediator = mediator | |||
} | |||
} | |||
/// متعددة functionalities تستخدم العناصر الحقيقية وظيفيات. | |||
/// ولا تعتمد هذه العناصر على عناصر غيرها ولا على أي فئة وسيط حقيقية. | |||
class Component1: BaseComponent { | |||
func doA() { | |||
print("Component 1 does A.") | |||
mediator?.notify(sender: self, event: "A") | |||
} | |||
func doB() { | |||
print("Component 1 does B.\n") | |||
mediator?.notify(sender: self, event: "B") | |||
} | |||
} | |||
class Component2: BaseComponent { | |||
func doC() { | |||
print("Component 2 does C.") | |||
mediator?.notify(sender: self, event: "C") | |||
} | |||
func doD() { | |||
print("Component 2 does D.") | |||
mediator?.notify(sender: self, event: "D") | |||
} | |||
} | |||
/// لنرى كيف سيعمل كل هذا.. | |||
class MediatorConceptual: XCTestCase { | |||
func testMediatorConceptual() { | |||
let component1 = Component1() | |||
let component2 = Component2() | |||
let mediator = ConcreteMediator(component1, component2) | |||
print("Client triggers operation A.") | |||
component1.doA() | |||
print("\nClient triggers operation D.") | |||
component2.doD() | |||
print(mediator) | |||
} | |||
} | |||
</syntaxhighlight> | |||
==== Output.txt: نتائج التنفيذ ==== | |||
<syntaxhighlight lang="text"> | |||
Client triggers operation A. | |||
Component 1 does A. | |||
Mediator reacts on A and triggers following operations: | |||
Component 2 does C. | |||
Client triggers operation D. | |||
Component 2 does D. | |||
Mediator reacts on D and triggers following operations: | |||
Component 1 does B. | |||
Component 2 does C. | |||
</syntaxhighlight> | |||
=== مثال واقعي === | |||
==== Example.swift: مثال واقعي ==== | |||
<syntaxhighlight lang="swift"> | |||
import XCTest | |||
class MediatorRealWorld: XCTestCase { | |||
func test() { | |||
let newsArray = [News(id: 1, title: "News1", likesCount: 1), | |||
News(id: 2, title: "News2", likesCount: 2)] | |||
let numberOfGivenLikes = newsArray.reduce(0, { $0 + $1.likesCount }) | |||
let mediator = ScreenMediator() | |||
let feedVC = NewsFeedViewController(mediator, newsArray) | |||
let newsDetailVC = NewsDetailViewController(mediator, newsArray.first!) | |||
let profileVC = ProfileViewController(mediator, numberOfGivenLikes) | |||
mediator.update([feedVC, newsDetailVC, profileVC]) | |||
feedVC.userLikedAllNews() | |||
feedVC.userDislikedAllNews() | |||
} | |||
} | |||
class NewsFeedViewController: ScreenUpdatable { | |||
private var newsArray: [News] | |||
private weak var mediator: ScreenUpdatable? | |||
init(_ mediator: ScreenUpdatable?, _ newsArray: [News]) { | |||
self.newsArray = newsArray | |||
self.mediator = mediator | |||
} | |||
func likeAdded(to news: News) { | |||
print("News Feed: Received a liked news model with id \(news.id)") | |||
for var item in newsArray { | |||
if item == news { | |||
item.likesCount += 1 | |||
} | |||
} | |||
} | |||
func likeRemoved(from news: News) { | |||
print("News Feed: Received a disliked news model with id \(news.id)") | |||
for var item in newsArray { | |||
if item == news { | |||
item.likesCount -= 1 | |||
} | |||
} | |||
} | |||
func userLikedAllNews() { | |||
print("\n\nNews Feed: User LIKED all news models") | |||
print("News Feed: I am telling to mediator about it...\n") | |||
newsArray.forEach({ mediator?.likeAdded(to: $0) }) | |||
} | |||
func userDislikedAllNews() { | |||
print("\n\nNews Feed: User DISLIKED all news models") | |||
print("News Feed: I am telling to mediator about it...\n") | |||
newsArray.forEach({ mediator?.likeRemoved(from: $0) }) | |||
} | |||
} | |||
class NewsDetailViewController: ScreenUpdatable { | |||
private var news: News | |||
private weak var mediator: ScreenUpdatable? | |||
init(_ mediator: ScreenUpdatable?, _ news: News) { | |||
self.news = news | |||
self.mediator = mediator | |||
} | |||
func likeAdded(to news: News) { | |||
print("News Detail: Received a liked news model with id \(news.id)") | |||
if self.news == news { | |||
self.news.likesCount += 1 | |||
} | |||
} | |||
func likeRemoved(from news: News) { | |||
print("News Detail: Received a disliked news model with id \(news.id)") | |||
if self.news == news { | |||
self.news.likesCount -= 1 | |||
} | |||
} | |||
} | |||
class ProfileViewController: ScreenUpdatable { | |||
private var numberOfGivenLikes: Int | |||
private weak var mediator: ScreenUpdatable? | |||
init(_ mediator: ScreenUpdatable?, _ numberOfGivenLikes: Int) { | |||
self.numberOfGivenLikes = numberOfGivenLikes | |||
self.mediator = mediator | |||
} | |||
func likeAdded(to news: News) { | |||
print("Profile: Received a liked news model with id \(news.id)") | |||
numberOfGivenLikes += 1 | |||
} | |||
func likeRemoved(from news: News) { | |||
print("Profile: Received a disliked news model with id \(news.id)") | |||
numberOfGivenLikes -= 1 | |||
} | |||
} | |||
protocol ScreenUpdatable: class { | |||
func likeAdded(to news: News) | |||
func likeRemoved(from news: News) | |||
} | |||
class ScreenMediator: ScreenUpdatable { | |||
private var screens: [ScreenUpdatable]? | |||
func update(_ screens: [ScreenUpdatable]) { | |||
self.screens = screens | |||
} | |||
func likeAdded(to news: News) { | |||
print("Screen Mediator: Received a liked news model with id \(news.id)") | |||
screens?.forEach({ $0.likeAdded(to: news) }) | |||
} | |||
func likeRemoved(from news: News) { | |||
print("ScreenMediator: Received a disliked news model with id \(news.id)") | |||
screens?.forEach({ $0.likeRemoved(from: news) }) | |||
} | |||
} | |||
struct News: Equatable { | |||
let id: Int | |||
let title: String | |||
var likesCount: Int | |||
/// خصائص أخرى. | |||
static func == (left: News, right: News) -> Bool { | |||
return left.id == right.id | |||
} | |||
} | |||
</syntaxhighlight> | |||
==== Output.txt: نتائج التنفيذ ==== | |||
<syntaxhighlight lang="text"> | |||
News Feed: User LIKED all news models | |||
News Feed: I am telling to mediator about it... | |||
Screen Mediator: Received a liked news model with id 1 | |||
News Feed: Received a liked news model with id 1 | |||
News Detail: Received a liked news model with id 1 | |||
Profile: Received a liked news model with id 1 | |||
Screen Mediator: Received a liked news model with id 2 | |||
News Feed: Received a liked news model with id 2 | |||
News Detail: Received a liked news model with id 2 | |||
Profile: Received a liked news model with id 2 | |||
News Feed: User DISLIKED all news models | |||
News Feed: I am telling to mediator about it... | |||
ScreenMediator: Received a disliked news model with id 1 | |||
News Feed: Received a disliked news model with id 1 | |||
News Detail: Received a disliked news model with id 1 | |||
Profile: Received a disliked news model with id 1 | |||
ScreenMediator: Received a disliked news model with id 2 | |||
News Feed: Received a disliked news model with id 2 | |||
News Detail: Received a disliked news model with id 2 | |||
Profile: Received a disliked news model with id 2 | |||
</syntaxhighlight> | |||
== الاستخدام في لغة TypeScript == | |||
'''المستوى:''' ★ ★ ☆ | |||
'''الانتشار:''' ☆ ☆ ☆ | |||
'''أمثلة الاستخدام:''' لعل تسهيل التواصل بين عناصر الواجهة الرسومية لبرنامج ما هو أكثر مثال منتشر لنمط الوسيط في لغة TypeScript، ويكون مرادف الوسيط هو الجزء المتحكم في نمط MVC. | |||
=== مثال تصوري === | |||
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية: | |||
* ما الفئات التي يتكون منها؟ | |||
* ما الأدوار التي تلعبها هذه الفئات؟ | |||
* كيف ترتبط عناصر النمط ببعضها؟ | |||
==== index.ts: مثال تصوري ==== | |||
<syntaxhighlight lang="typescript"> | |||
/** | |||
* تصرِّح واجهة الوسيط عن أسلوب تستخدمه العناصر لإشعار الوسيط | |||
* بالأحداث المختلفة، وقد يتفاعل الوسيط مع هذه الأحداث ويمرر التنفيذ إلى | |||
* عناصر أخرى. | |||
*/ | |||
interface Mediator { | |||
notify(sender: object, event: string): void; | |||
} | |||
/** | |||
* سلوكًا تعاونيًا من خلال mediators يستخدم الوسطاء | |||
* التنسيق بين عدة عناصر. | |||
*/ | |||
class ConcreteMediator implements Mediator { | |||
private component1: Component1; | |||
private component2: Component2; | |||
constructor(c1: Component1, c2: Component2) { | |||
this.component1 = c1; | |||
this.component1.setMediator(this); | |||
this.component2 = c2; | |||
this.component2.setMediator(this); | |||
} | |||
public notify(sender: object, event: string): void { | |||
if (event === 'A') { | |||
console.log('Mediator reacts on A and triggers following operations:'); | |||
this.component2.doC(); | |||
} | |||
if (event === 'D') { | |||
console.log('Mediator reacts on D and triggers following operations:'); | |||
this.component1.doB(); | |||
this.component2.doC(); | |||
} | |||
} | |||
} | |||
/** | |||
* الوظيفية الأساسية لتخزين نسخة من الوسيط Base Component يوفر العنصر الأساسي | |||
* داخل كائنات العنصر. | |||
*/ | |||
class BaseComponent { | |||
protected mediator: Mediator; | |||
constructor(mediator: Mediator = null) { | |||
this.mediator = mediator; | |||
} | |||
public setMediator(mediator: Mediator): void { | |||
this.mediator = mediator; | |||
} | |||
} | |||
/** | |||
* متعددة functionalities تستخدم العناصر الحقيقية وظيفيات. | |||
* ولا تعتمد هذه العناصر على عناصر غيرها ولا على أي فئة وسيط حقيقية. | |||
*/ | |||
class Component1 extends BaseComponent { | |||
public doA(): void { | |||
console.log('Component 1 does A.'); | |||
this.mediator.notify(this, 'A'); | |||
} | |||
public doB(): void { | |||
console.log('Component 1 does B.'); | |||
this.mediator.notify(this, 'B'); | |||
} | |||
} | |||
class Component2 extends BaseComponent { | |||
public doC(): void { | |||
console.log('Component 2 does C.'); | |||
this.mediator.notify(this, 'C'); | |||
} | |||
public doD(): void { | |||
console.log('Component 2 does D.'); | |||
this.mediator.notify(this, 'D'); | |||
} | |||
} | |||
/** | |||
* شيفرة العميل. | |||
*/ | |||
const c1 = new Component1(); | |||
const c2 = new Component2(); | |||
const mediator = new ConcreteMediator(c1, c2); | |||
console.log('Client triggers operation A.'); | |||
c1.doA(); | |||
console.log(''); | |||
console.log('Client triggers operation D.'); | |||
c2.doD(); | |||
</syntaxhighlight> | |||
==== Output.txt: نتائج التنفيذ ==== | |||
<syntaxhighlight lang="text"> | |||
Client triggers operation A. | |||
Component 1 does A. | |||
Mediator reacts on A and triggers following operations: | |||
Component 2 does C. | |||
Client triggers operation D. | |||
Component 2 does D. | |||
Mediator reacts on D and triggers following operations: | |||
Component 1 does B. | |||
Component 2 does C. | |||
</syntaxhighlight> | |||
== انظر أيضًا == | |||
* [[Design Patterns/memento|نمط التذكرة Memento.]] | |||
* [[Design Patterns/state|نمط الحالة State]]. | |||
* [[Design Patterns/iterator|نمط المكرر Iterator]]. | |||
== مصادر == | |||
* [https://refactoring.guru/design-patterns/mediator توثيق نمط الوسيط في موقع refactoring.guru.] | |||
[[تصنيف:Design Patterns]] |
المراجعة الحالية بتاريخ 10:45، 7 أكتوبر 2022
نمط الوسيط هو نمط تصميم سلوكي يسمح لك بتقليل الاعتماديات الفوضوية بين الكائنات، إذ يقيد عمليات التواصل المباشرة بينها ويجبرها على التواصل من خلال كائن وسيط.
المشكلة
لنقل أن لديك صندوقًا حواريًا لإنشاء وتعديل حسابات المستخدمين، به عناصر مختلفة من متحكمات الاستمارات، مثل الحقول النصية ومربعات الاختيار والأزرار وغيرها. (انظر ش.1)
قد تتفاعل بعض عناصر الاستمارة مع بعضها البعض، فتحديد المربع أمام خيار "لدي كلب" مثلًا قد يظهر حقلًا نصيًا مخفيًا لإدخال اسم ذاك الكلب، والنقر على زر "إرسال" يستدعي تحققه من جميع القيم المُدخلة في الاستمارة قبل حفظ البيانات. (انظر ش.2)
وبتطبيق هذا المنطق مباشرة داخل شيفرة عناصر الاستمارة فإنك تجعل فئات تلك العناصر أصعب في إعادة الاستخدام في استمارات أخرى داخل التطبيق، فقد لا تتمكن من استخدام فئة مربع الاختيار تلك داخل استمارة أخرى بسبب أنك ربطتها بالحقل النصي الذي سيظهر بعد تحديد مربع الاختيار منتظرًا إدخالك لنص "اسم الكلب في هذه الحالة من المثال السابق". فيكون الخيار الذي لديك هو أن تستخدم كل تلك الفئات التي استخدمتها لإخراج حساب المستخدم، أو تتركها بالكلية.
الحل
يقترح نمط الوسيط أنك يجب أن توقف جميع الاتصالات المباشرة بين العناصر التي تريدها أن تكون مستقلة بنفسها، وتجعلها تتواصل فيما بينها بشكل غير مباشر من خلال استدعاء كائن وسيط خاص يعيد توجيه الاستدعاءات إلى العناصر المناسبة. وهكذا تعتمد العناصر على فئة وسيط واحدة بدلًا من أن تكون مرتبطة بعشرات العناصر الأخرى. (انظر ش.3)
وفي مثالنا الخاص باستمارة تعديل الحساب فإن فئة الصندوق الحواري نفسها قد تتصرف كوسيط، وهي على الأرجح تعرف جميع عناصرها الفرعية، لذا فلن تحتاج أن تدخِل اعتماديات جديدة في تلك الفئة.
لعل أكثر تغيير تلاحظه هنا هو ذاك الذي يحدث لعناصر الاستمارة الحقيقية، فمثلًا ما كان يحدث عادة عند النقر على زر "إرسال Submit" أن الزر ينفذ عملية تحقق كاملة لكل القيم التي أدخلها المستخدم، وذلك في كل مرة يضغط فيها زر إرسال. لكن بعد جعله مرتبطًا بالصندوق الحواري فقط، فإن وظيفته الوحيدة صارت تنبيه الصندوق الحواري بنقرة المستخدم، وينفذ الصندوق بعدها عمليات التحقق أو يمررها إلى العناصر المنفردة، ومن ثم فإن الزر يعتمد الآن على فئة الصندوق الحواري وحدها بدلًا من ارتباطه بعشرات العناصر من الاستمارة.
تستطيع المضي في هذا المنطق وتجعل الاعتمادية أكثر مرونة باستخراج الواجهة المشتركة لكل أنواع الصناديق الحوارية، وينبغي أن تصرح الواجهة عن أسلوب الإشعار الذي ستستخدمه كل عناصر الاستمارة لإشعار الصندوق الحواري بالأحداث التي تقع لتلك العناصر، وعليه ينبغي أن يستطيع زر الإرسال الآن أن يعمل مع أي صندوق حواري يستخدم تلك الواجهة. وهكذا يسمح لك نمط الوسيط بتغليف شبكة علاقات بين عدة كائنات داخل كائنِ وسيطٍ واحد، وفائدة هذا أنه كلما قلت الاعتماديات (dependencies) في فئة ما، صار تعديلها أسهل، وكذلك توسيعها وإعادة استخدامها.
مثال من الواقع
لا يتواصل الطيارون الذين على وشك الهبوط أو المغادرة مباشرة مع بعضهم لتحديد من يهبط قبل من ومن يقلع قبل غيره، بل يخاطبون مسؤولًا في حركة المرور الخاصة بالطائرات على مدرجات المطار وفي سمائه، يجلس في برج طويل على بعد مناسب من المدرجات. لكن إن لم يوجد ذلك المسؤول فسيستغرق الطيارون وقتًا طويلًا في مناقشة أولويات الهبوط وأماكنه إضافة إلى ضرورة علمهم بكل طائرة موجودة في محيط المطار أو تقترب منه ، مما يزيد من معدلات الحوادث بشكل مخيف في النهاية.
وتنتهي مهمة برج المراقبة بعد ضبط عمليات الهبوط والإقلاع، فلا يتحكم بشيء من رحلة الطائرة بعد ذلك، إذ أنه موجود لتنفيذ قوانين تحكم حركة مرور الطائرات من وإلى وعلى المدرجات، حيث يكون عدد المشاركين في تلك العملية كبيرًا على طيار بمفرده داخل طائرته.
البنية
- العناصر (Components) هي فئات مختلفة تحتوي على بعض منطق العمل (Business Logic)، وكل عنصر به مرجع إلى وسيط (mediator)، صُرِّح عنه مع نوع واجهة الوسيط. ولا يشعر العنصر بفئة الوسيط الحقيقية، لهذا تستطيع إعادة استخدام العنصر في برامج أخرى من خلال ربطها بوسيط مختلف.
- واجهة الوسيط (Mediator) تصرح عن أساليب للتواصل مع العناصر، وتتضمن عادة أسلوب إشعار واحد. وقد تمرر العناصر أي سياق كوسائط (arguments) لهذا الأسلوب، بما في ذلك كائناتها الخاصة، لكن بطريقة لا تسمح بحدوث تكرار بين العنصر المستقبِل وفئة المرسِل.
- الوسيط الحقيقي (Concrete Mediator) يختزل العلاقات بين العناصر المختلفة، ويحتفظ الوسيط الحقيقي عادة بمراجع إلى كل العناصر التي يديرها، وأحيانًا يدير دورة حياتها كذلك.
- يجب ألا تشعر العناصر بوجود العناصر الأخرى، فإن حدث شيء مهم لعنصر أو حدث داخله، فيجب أن ينبه الوسيط حصرًا، ويستطيع الوسيط التعرف على المرسل بمجرد استلام الإشعار، وهذا يكون كافيًا في الغالب لتقرير أي عنصر يجب أن يُشغَّل في المقابل.
لتقريب الصورة، فإن الأمر يبدو من منظور العنصر كأنه صندوق أسود، فلا يعرف المرسل من سيتعامل مع طلبه، ولا يعرف المستقبِل من أرسل الطلب أصلًا.
مثال وهمي
يساعدك نمط الوسيط في هذا المثال على التخلص من الاعتماديات المزدوجة بين فئات الواجهة الرسومية المختلفة والأزرار ومربعات الاختيار والحقول النصية كذلك.
لا يتواصل العنصر الذي يشغِّله المستخدم مع العناصر الأخرى بشكل مباشر حتى لو بدا أنه يفترض به ذلك، ولا يحتاج سوى أن ينبه الوسيط الخاص به بذلك الحدث، مرسلًا أي بيانات سياقية أخرى مع ذلك التنبيه.
وفي هذا المثال يتصرف الصندوق الحواري للاستيثاق كوسيط، فهو يعرف كيف يجب أن تتعاون العناصر الحقيقية، ومن ثم ييسر عملية التواصل غير المباشر. ويقرر الصندوق عند استلام تنبيه بحدثٍ أيَّ العناصر التي ستتعامل مع ذلك الحدث، ومن ثم يعيد توجيه الاستدعاء وفقًا لذلك.
// تصرح واجهة الوسيط عن أسلوب تستخدمه العناصر لتنبيه الوسيط بالأحداث
// المختلفة، وقد يتفاعل الوسيط مع تلك الأحداث ويمرر التنفيذ إلى عناصر
// أخرى.
interface Mediator is
method notify(sender: Component, event: string)
// فئة الوسيط الحقيقي. الشبكة المتداخلة من الاتصالات بين العناصر
// الفردية قد حُلَّت ونُقلت إلى الوسيط.
class AuthenticationDialog implements Mediator is
private field title: string
private field loginOrRegisterChkBx: Checkbox
private field loginUsername, loginPassword: Textbox
private field registrationUsername, registrationPassword
private field registrationEmail: Textbox
private field okBtn, cancelBtn: Button
constructor AuthenticationDialog() is
// أنشئ جميع كائنات العناصر ومرر الوسيط الحالي إلى منشئاتها
// لتحقيق الروابط.
// حين يحدث شيء ما لعنصر ما فإنه ينبه الوسيط، وعندما يستلم الوسيط
// التنبيه فقد يتصرف بنفسه أو يمرر الطلب إلى مكون آخر.
method notify(sender, event) is
if (sender == loginOrRegisterChkBx and event == "check")
if (loginOrRegisterChkBx.checked)
title = "Log in"
// 1. أظهر عناصر استمارة تسجيل الدخول.
// 2. أخْفِ عناصر استمارة التسجيل.
else
title = "Register"
// 1. أظهر عناصر استمارة تسجيل الدخول.
// 2. أخْفِ عناصر استمارة التسجيل.
if (sender == okBtn && event == "click")
if (loginOrRegister.checked)
// حاول إيجاد مستخدم عن طريق بيانات تسجيل الدخول.
if (!found)
// أظهر رسالة خطأ فوق حقل تسجيل الدخول.
else
// 1. أنشئ حساب مستخدم باستخدام بيانات من حقول التسجيل.
// 2. سجِّل دخول هذا المستخدم.
// ...
// تتواصل العناصر مع الوسيط باستخدام واجهة الوسيط.
// ويمكنك استخدام نفس العناصر في سياقات أخرى من خلال
// ربطها بكائنات وسيط مختلفة.
class Component is
field dialog: Mediator
constructor Component(dialog) is
this.dialog = dialog
method click() is
dialog.notify(this, "click")
method keypress() is
dialog.notify(this, "keypress")
// لا تتواصل العناصر الحقيقية مع بعضها، وليس لها سوى قناة اتصال وحيدة
// وهي إرسال التنبيهات إلى الوسيط.
class Button extends Component is
// ...
class Textbox extends Component is
// ...
class Checkbox extends Component is
method check() is
dialog.notify(this, "check")
// ...
قابلية الاستخدام
- استخدم نمط الوسيط عندما يكون من الصعب تغيير بعض الفئات بسبب أن لها ارتباطات كثيرة مع فئات أخرى.
يسمح لك النمط باستخراج جميع العلاقات بين الفئات إلى فئة منفصلة عازلًا أي تغييرات على عنصر بعينه عن بقية الشيفرة.
- استخدم النمط عندما لا تستطيع إعادة استخدام عنصر في برنامج مختلف بسبب أنه يعتمد بشدة على عناصر أخرى.
تصبح العناصر المنفردة بعد تطبيق نمط الوسيط غير مدركة للعناصر الأخرى رغم أنها تستطيع التواصل بين بعضها البعض -ولو كان بشكل غير مباشر- من خلال كائن وسيط. ولكي تعيد استخدام عنصر في برنامج مختلف فإنك تحتاج إلى إضافة فئة وسيط جديدة إليه.
- استخدم الوسيط حين تنشئ فئات فرعية كثيرة لعناصر من أجل إعادة استخدام بعض السلوك الأساسي (basic behavior) في سياقات متعددة.
بما أن جميع العلاقات بين العناصر محتواة داخل الوسيط، فمن السهل تحديد طرق جديدة كليًا لتعاون تلك العناصر من خلال إدخال فئات وسيط جديدة دون الحاجة إلى تغيير العناصر أنفسها.
كيفية الاستخدام
- حدد مجموعة فئات مرتبطة بشدة ببعضها ستستفيد من كونها مستقلة (من أجل سهولة الصيانة مثلًا أو إعادة استخدام بسيطة لتلك الفئات).
- صرِّح عن واجهة الوسيط وَصِفْ بروتوكول التواصل الذي تريده بين الوسطاء والعناصر الأخرى، ويكفي في أغلب الحالات أسلوب واحد لاستقبال التنبيهات من العناصر. هذه الواجهة ضرورية عندما تريد إعادة استخدام فئات العناصر في سياقات مختلفة، فطالما أن العنصر يعمل مع وسيطه من خلال واجهة عامة (generic) فيمكنك ربط ذلك العنصر مع استخدام مختلف للوسيط.
- استخدم فئة الوسيط الحقيقية، ستستفيد تلك الفئة من تخزين مراجع إلى كل العناصر التي تديرها.
- تستطيع جعل الوسيط مسؤولًا عن إنشاء وإلغاء كائنات العنصر، وعندها قد يتشابه الوسيط مع نمط المصنع المجرد أو الواجهة.
- يجب أن تخزن العناصر مرجعًا إلى كائن الوسيط، ويتم الاتصال غالبًا في منشئ العنصر حيث يُمرَّر كائن الوسيط كمُعطى (argument).
- غيِّر شيفرة العناصر بحيث تستدعي أسلوب الإشعار الخاص بالوسيط بدلًا من الأساليب التي في العناصر الأخرى. أيضًا، استخرج الشيفرة التي تضمن استدعاء عناصر أخرى إلى داخل فئة الوسيط، ونفذ هذه الشيفرة كلما استقبل الوسيط إشعارات من ذلك العنصر.
المزايا والعيوب
المزايا
- مبدأ المسؤولية الواحدة. تستطيع استخراج عمليات التواصل بين العناصر المختلفة إلى مكان واحد، مما ييسر استيعابها وصيانتها.
- مبدأ المفتوح/المغلق. تستطيع إدخال وسطاء جدد دون الحاجة إلى تغيير العناصر الحقيقية.
- تستطيع تقليل الارتباط (coupling) بين العناصر المختلفة لبرنامج ما.
- تستطيع إعادة استخدام العناصر المنفردة بشكل أسهل.
العيوب
- قد يتطور الوسيط مع الوقت إلى كائن إلهي (god object).
العلاقات مع الأنماط الأخرى
- تقدم أنماط سلسلة المسؤولية والأمر والوسيط والمراقب طرقًا مختلفة لتوصيل مرسِلات الطلبات ومستقبلاتها ببعضها، وذلك على النحو التالي:
- تمرر سلسلة المسؤولية الطلب بشكل تسلسلي مع سلسلة ديناميكية من المستقبلات المحتملة إلى أن يعالجها أحد المستقبلات.
- ينشئ الأمر اتصالات أحادية الاتجاه بين المرسِلات والمستقبِلات.
- يزيل الوسيط الاتصالات المباشرة بين المرسِلات والمستقبلات مجبرًا إياها على التواصل بشكل غير مباشر من خلال كائن وسيط.
- يسمح المراقب للمستقبلات بالاشتراك في استقبال الطلبات وكذلك إلغاء الاشتراك بمرونة.
- يتشابه نمطا الواجهة والوسيط في الوظائف، فكلاهما يحاول تنظيم التعاون بين فئات كثيرة مرتبطة ببعضها، وذلك على النحو التالي:
- يحدد نمط الواجهة واجهة مبسطة لنظام فرعي من الكائنات، لكنه لا يقدم أي وظيفة جديدة، بل إن النظام الفرعي نفسه غير مدرك لوجود الواجهة، وتستطيع الكائنات داخل النظام الفرعي أن تتواصل بشكل مباشر.
- يمركز الوسيط الاتصالات بين عناصر النظام، وتدرك تلك العناصر وجود كائن الوسيط، ولا تتواصل بين بعضها بشكل مباشر.
- عادة ما يكون الفرق بين نمطي الوسيط والمراقب محيرًا، ففي أغلب الحالات تستطيع استخدام نمط واحد فحسب منهما، لكن في ظروف تشغيل أخرى قد تستطيع استخدام كليهما معًا، وذلك على النحو التالي:
الهدف الأساسي من الوسيط هو إزالة الاعتماديات المتبادلة (mutual dependencies) بين مجموعة من عناصر النظام، وتصبح تلك العناصر معتمدة على كائن وسيط واحد. أما الهدف من المراقب هو إنشاء اتصالات مرنة أحادية الاتجاه بين الكائنات، حيث تتصرف بعض الكائنات كتوابع لغيرها.
ولدينا استخدام شائع لنمط الوسيط يعتمد فيه على نمط المراقب، يلعب فيه كائن الوسيط دور الناشر (publisher)، ويتصرف كل عنصر كمشترك يستطيع الاشتراك في أحداث الوسيط (mediator events) وكذلك إلغاء الاشتراك منها، وتطبيق نمط الوسيط بهذا الشكل يجعله قريب الشبه جدًا من نمط المراقب. تذكر أنك تستطيع استخدام نمط الوسيط بطرق أخرى، فيمكنك مثلًا ربط كل العناصر إلى نفس الكائن الوسيط، ورغم أن هذا الاستخدام لا يشبه نمط المراقب لكن لا يزال صورة من نمط الوسيط.
وتخيل الآن برنامجًا تلعب فيه كل العناصر دور الناشر، سامحة باتصالات مرنة بين بعضها البعض، عندئذ لن يكون هناك كائن وسيط مركزي، بل مجموعة موزعة من المراقبين.
الاستخدام في لغة جافا
المستوى: ★ ★ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: لعل تسهيل التواصل بين عناصر الواجهة الرسومية لبرنامج ما هو ـكثر مثال منتشر لنمط الوسيط في لغة جافا. ويكون مرادف الوسيط هو الجزء المتحكم في نمط MVC. إليك بعض الأمثلة على النمط في مكتبات جافا:
- java.util.Timer (جميع أساليب
()schedualXXX
). - ()java.util.concurrent.Executor#execute.
- java.util.concurrent.ExecutorService (أساليب
()invokeXXX
و()submit
). - java.util.concurrent.ScheduledExecutorService (جميع أساليب
()schedualXXX
). - ()java.lang.reflect.Method#invoke.
تطبيق المذكرة
يوضح هذا المثال كيفية تنظيم كثير من عناصر الواجهة الرسومية كي تتعاون بمساعدة الوسيط بدون أن تعتمد على بعضها البعض.
العناصر: الفئات الزميلة colleague classes
components/Component.java
package refactoring_guru.mediator.example.components;
import refactoring_guru.mediator.example.mediator.Mediator;
/**
* واجهة العنصر المشتركة.
*/
public interface Component {
void setMediator(Mediator mediator);
String getName();
}
components/AddButton.java
package refactoring_guru.mediator.example.components;
import refactoring_guru.mediator.example.mediator.Mediator;
import refactoring_guru.mediator.example.mediator.Note;
import javax.swing.*;
import java.awt.event.ActionEvent;
/**
* لا تتواصل العناصر الحقيقية مع بعضها البعض، والطريقة الوحيدة للتواصل
* هي عبر إرسال الطلبات إلى الوسيط.
*/
public class AddButton extends JButton implements Component {
private Mediator mediator;
public AddButton() {
super("Add");
}
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
@Override
protected void fireActionPerformed(ActionEvent actionEvent) {
mediator.addNewNote(new Note());
}
@Override
public String getName() {
return "AddButton";
}
}
components/DeleteButton.java
package refactoring_guru.mediator.example.components;
import refactoring_guru.mediator.example.mediator.Mediator;
import javax.swing.*;
import java.awt.event.ActionEvent;
/**
* لا تتواصل العناصر الحقيقية مع بعضها البعض، والطريقة الوحيدة للتواصل
* هي عبر إرسال الطلبات إلى الوسيط.
*/
public class DeleteButton extends JButton implements Component {
private Mediator mediator;
public DeleteButton() {
super("Del");
}
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
@Override
protected void fireActionPerformed(ActionEvent actionEvent) {
mediator.deleteNote();
}
@Override
public String getName() {
return "DelButton";
}
}
components/Filter.java
package refactoring_guru.mediator.example.components;
import refactoring_guru.mediator.example.mediator.Mediator;
import refactoring_guru.mediator.example.mediator.Note;
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
/**
* لا تتواصل العناصر الحقيقية مع بعضها البعض، والطريقة الوحيدة للتواصل
* هي عبر إرسال الطلبات إلى الوسيط.
*/
public class Filter extends JTextField implements Component {
private Mediator mediator;
private ListModel listModel;
public Filter() {}
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
@Override
protected void processComponentKeyEvent(KeyEvent keyEvent) {
String start = getText();
searchElements(start);
}
public void setList(ListModel listModel) {
this.listModel = listModel;
}
private void searchElements(String s) {
if (listModel == null) {
return;
}
if (s.equals("")) {
mediator.setElementsList(listModel);
return;
}
ArrayList<Note> notes = new ArrayList<>();
for (int i = 0; i < listModel.getSize(); i++) {
notes.add((Note) listModel.getElementAt(i));
}
DefaultListModel<Note> listModel = new DefaultListModel<>();
for (Note note : notes) {
if (note.getName().contains(s)) {
listModel.addElement(note);
}
}
mediator.setElementsList(listModel);
}
@Override
public String getName() {
return "Filter";
}
}
components/List.java
package refactoring_guru.mediator.example.components;
import refactoring_guru.mediator.example.mediator.Mediator;
import refactoring_guru.mediator.example.mediator.Note;
import javax.swing.*;
/**
* لا تتواصل العناصر الحقيقية مع بعضها البعض، والطريقة الوحيدة للتواصل
* هي عبر إرسال الطلبات إلى الوسيط.
*/
@SuppressWarnings("unchecked")
public class List extends JList implements Component {
private Mediator mediator;
private final DefaultListModel LIST_MODEL;
public List(DefaultListModel listModel) {
super(listModel);
this.LIST_MODEL = listModel;
setModel(listModel);
this.setLayoutOrientation(JList.VERTICAL);
Thread thread = new Thread(new Hide(this));
thread.start();
}
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
public void addElement(Note note) {
LIST_MODEL.addElement(note);
int index = LIST_MODEL.size() - 1;
setSelectedIndex(index);
ensureIndexIsVisible(index);
mediator.sendToFilter(LIST_MODEL);
}
public void deleteElement() {
int index = this.getSelectedIndex();
try {
LIST_MODEL.remove(index);
mediator.sendToFilter(LIST_MODEL);
} catch (ArrayIndexOutOfBoundsException ignored) {}
}
public Note getCurrentElement() {
return (Note)getSelectedValue();
}
@Override
public String getName() {
return "List";
}
private class Hide implements Runnable {
private List list;
Hide(List list) {
this.list = list;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(300);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
if (list.isSelectionEmpty()) {
mediator.hideElements(true);
} else {
mediator.hideElements(false);
}
}
}
}
}
components/SaveButton.java
package refactoring_guru.mediator.example.components;
import refactoring_guru.mediator.example.mediator.Mediator;
import javax.swing.*;
import java.awt.event.ActionEvent;
/**
* لا تتواصل العناصر الحقيقية مع بعضها البعض، والطريقة الوحيدة للتواصل
* هي عبر إرسال الطلبات إلى الوسيط.
*/
public class SaveButton extends JButton implements Component {
private Mediator mediator;
public SaveButton() {
super("Save");
}
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
@Override
protected void fireActionPerformed(ActionEvent actionEvent) {
mediator.saveChanges();
}
@Override
public String getName() {
return "SaveButton";
}
}
components/TextBox.java
package refactoring_guru.mediator.example.components;
import refactoring_guru.mediator.example.mediator.Mediator;
import javax.swing.*;
import java.awt.event.KeyEvent;
/**
* لا تتواصل العناصر الحقيقية مع بعضها البعض، والطريقة الوحيدة للتواصل
* هي عبر إرسال الطلبات إلى الوسيط.
*/
public class TextBox extends JTextArea implements Component {
private Mediator mediator;
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
@Override
protected void processComponentKeyEvent(KeyEvent keyEvent) {
mediator.markNote();
}
@Override
public String getName() {
return "TextBox";
}
}
components/Title.java
package refactoring_guru.mediator.example.components;
import refactoring_guru.mediator.example.mediator.Mediator;
import javax.swing.*;
import java.awt.event.KeyEvent;
/**
* لا تتواصل العناصر الحقيقية مع بعضها البعض، والطريقة الوحيدة للتواصل
* هي عبر إرسال الطلبات إلى الوسيط.
*/
public class Title extends JTextField implements Component {
private Mediator mediator;
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
@Override
protected void processComponentKeyEvent(KeyEvent keyEvent) {
mediator.markNote();
}
@Override
public String getName() {
return "Title";
}
}
الوسيط
mediator/Mediator.java: يحدد واجهة الوسيط المشتركة
package refactoring_guru.mediator.example.mediator;
import refactoring_guru.mediator.example.components.Component;
import javax.swing.*;
/**
* واجهة الوسيط المشتركة.
*/
public interface Mediator {
void addNewNote(Note note);
void deleteNote();
void getInfoFromList(Note note);
void saveChanges();
void markNote();
void clear();
void sendToFilter(ListModel listModel);
void setElementsList(ListModel list);
void registerComponent(Component component);
void hideElements(boolean flag);
void createGUI();
}
mediator/Editor.java: الوسيط الحقيقي
package refactoring_guru.mediator.example.mediator;
import refactoring_guru.mediator.example.components.*;
import refactoring_guru.mediator.example.components.Component;
import refactoring_guru.mediator.example.components.List;
import javax.swing.*;
import javax.swing.border.LineBorder;
import java.awt.*;
/**
* الوسيط الحقيقي، استُخرِجت كل عمليات التواصل العشوائية بين العناصر الحقيقية
* إلى الوسيط، والآن تتواصل العناصر مع الوسيط الذي يعرف كيف يعالج الطلبات.
*/
public class Editor implements Mediator {
private Title title;
private TextBox textBox;
private AddButton add;
private DeleteButton del;
private SaveButton save;
private List list;
private Filter filter;
private JLabel titleLabel = new JLabel("Title:");
private JLabel textLabel = new JLabel("Text:");
private JLabel label = new JLabel("Add or select existing note to proceed...");
/**
* هنا تسجيل العناصر من خلال الوسيط.
*/
@Override
public void registerComponent(Component component) {
component.setMediator(this);
switch (component.getName()) {
case "AddButton":
add = (AddButton)component;
break;
case "DelButton":
del = (DeleteButton)component;
break;
case "Filter":
filter = (Filter)component;
break;
case "List":
list = (List)component;
this.list.addListSelectionListener(listSelectionEvent -> {
Note note = (Note)list.getSelectedValue();
if (note != null) {
getInfoFromList(note);
} else {
clear();
}
});
break;
case "SaveButton":
save = (SaveButton)component;
break;
case "TextBox":
textBox = (TextBox)component;
break;
case "Title":
title = (Title)component;
break;
}
}
/**
* أساليب متعددة لمعالجة الطلبات من عناصر بعينها.
*/
@Override
public void addNewNote(Note note) {
title.setText("");
textBox.setText("");
list.addElement(note);
}
@Override
public void deleteNote() {
list.deleteElement();
}
@Override
public void getInfoFromList(Note note) {
title.setText(note.getName().replace('*', ' '));
textBox.setText(note.getText());
}
@Override
public void saveChanges() {
try {
Note note = (Note) list.getSelectedValue();
note.setName(title.getText());
note.setText(textBox.getText());
list.repaint();
} catch (NullPointerException ignored) {}
}
@Override
public void markNote() {
try {
Note note = list.getCurrentElement();
String name = note.getName();
if (!name.endsWith("*")) {
note.setName(note.getName() + "*");
}
list.repaint();
} catch (NullPointerException ignored) {}
}
@Override
public void clear() {
title.setText("");
textBox.setText("");
}
@Override
public void sendToFilter(ListModel listModel) {
filter.setList(listModel);
}
@SuppressWarnings("unchecked")
@Override
public void setElementsList(ListModel list) {
this.list.setModel(list);
this.list.repaint();
}
@Override
public void hideElements(boolean flag) {
titleLabel.setVisible(!flag);
textLabel.setVisible(!flag);
title.setVisible(!flag);
textBox.setVisible(!flag);
save.setVisible(!flag);
label.setVisible(flag);
}
@Override
public void createGUI() {
JFrame notes = new JFrame("Notes");
notes.setSize(960, 600);
notes.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JPanel left = new JPanel();
left.setBorder(new LineBorder(Color.BLACK));
left.setSize(320, 600);
left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS));
JPanel filterPanel = new JPanel();
filterPanel.add(new JLabel("Filter:"));
filter.setColumns(20);
filterPanel.add(filter);
filterPanel.setPreferredSize(new Dimension(280, 40));
JPanel listPanel = new JPanel();
list.setFixedCellWidth(260);
listPanel.setSize(320, 470);
JScrollPane scrollPane = new JScrollPane(list);
scrollPane.setPreferredSize(new Dimension(275, 410));
listPanel.add(scrollPane);
JPanel buttonPanel = new JPanel();
add.setPreferredSize(new Dimension(85, 25));
buttonPanel.add(add);
del.setPreferredSize(new Dimension(85, 25));
buttonPanel.add(del);
buttonPanel.setLayout(new FlowLayout());
left.add(filterPanel);
left.add(listPanel);
left.add(buttonPanel);
JPanel right = new JPanel();
right.setLayout(null);
right.setSize(640, 600);
right.setLocation(320, 0);
right.setBorder(new LineBorder(Color.BLACK));
titleLabel.setBounds(20, 4, 50, 20);
title.setBounds(60, 5, 555, 20);
textLabel.setBounds(20, 4, 50, 130);
textBox.setBorder(new LineBorder(Color.DARK_GRAY));
textBox.setBounds(20, 80, 595, 410);
save.setBounds(270, 535, 80, 25);
label.setFont(new Font("Verdana", Font.PLAIN, 22));
label.setBounds(100, 240, 500, 100);
right.add(label);
right.add(titleLabel);
right.add(title);
right.add(textLabel);
right.add(textBox);
right.add(save);
notes.setLayout(null);
notes.getContentPane().add(left);
notes.getContentPane().add(right);
notes.setResizable(false);
notes.setLocationRelativeTo(null);
notes.setVisible(true);
}
}
mediator/Note.java: فئة المذكرة
package refactoring_guru.mediator.example.mediator;
/**
* فئة المذكرة.
*/
public class Note {
private String name;
private String text;
public Note() {
name = "New note";
}
public void setName(String name) {
this.name = name;
}
public void setText(String text) {
this.text = text;
}
public String getName() {
return name;
}
public String getText() {
return text;
}
@Override
public String toString() {
return name;
}
}
Demo.java: شيفرة البدء (Initialization Code)
package refactoring_guru.mediator.example;
import refactoring_guru.mediator.example.components.*;
import refactoring_guru.mediator.example.mediator.Editor;
import refactoring_guru.mediator.example.mediator.Mediator;
import javax.swing.*;
/**
* فئة العرض، يجتمع كل شيء ها هنا.
*/
public class Demo {
public static void main(String[] args) {
Mediator mediator = new Editor();
mediator.registerComponent(new Title());
mediator.registerComponent(new TextBox());
mediator.registerComponent(new AddButton());
mediator.registerComponent(new DeleteButton());
mediator.registerComponent(new SaveButton());
mediator.registerComponent(new List(new DefaultListModel()));
mediator.registerComponent(new Filter());
mediator.createGUI();
}
}
OutputDemo.png: نتائج التنفيذ
الاستخدام في لغة #C
المستوى: ★ ★ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: لعل تسهيل التواصل بين عناصر الواجهة الرسومية لبرنامج ما هو أكثر مثال منتشر لنمط الوسيط في لغة #C. ويكون مرادف الوسيط هو الجزء المتحكم في نمط MVC.
مثال تصوري
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
Program.cs: مثال تصوري
using System;
namespace RefactoringGuru.DesignPatterns.Mediator.Conceptual
{
// تصرِّح واجهة الوسيط عن أسلوب تستخدمه العناصر لإشعار الوسيط
// بالأحداث المختلفة، وقد يتفاعل الوسيط مع هذه الأحداث ويمرر التنفيذ إلى
// عناصر أخرى.
public interface IMediator
{
void Notify(object sender, string ev);
}
// سلوكًا تعاونيًا من خلال mediators يستخدم الوسطاء
// التنسيق بين عدة عناصر.
class ConcreteMediator : IMediator
{
private Component1 _component1;
private Component2 _component2;
public ConcreteMediator(Component1 component1, Component2 component2)
{
this._component1 = component1;
this._component1.SetMediator(this);
this._component2 = component2;
this._component2.SetMediator(this);
}
public void Notify(object sender, string ev)
{
if (ev == "A")
{
Console.WriteLine("Mediator reacts on A and triggers folowing operations:");
this._component2.DoC();
}
if (ev == "D")
{
Console.WriteLine("Mediator reacts on D and triggers following operations:");
this._component1.DoB();
this._component2.DoC();
}
}
}
// الوظيفية الأساسية لتخزين نسخة من الوسيط Base Component يوفر العنصر الأساسي
// داخل كائنات العنصر.
class BaseComponent
{
protected IMediator _mediator;
public BaseComponent(IMediator mediator = null)
{
this._mediator = mediator;
}
public void SetMediator(IMediator mediator)
{
this._mediator = mediator;
}
}
// متعددة functionalities تستخدم العناصر الحقيقية وظيفيات.
// ولا تعتمد هذه العناصر على عناصر غيرها ولا على أي فئة وسيط حقيقية.
class Component1 : BaseComponent
{
public void DoA()
{
Console.WriteLine("Component 1 does A.");
this._mediator.Notify(this, "A");
}
public void DoB()
{
Console.WriteLine("Component 1 does B.");
this._mediator.Notify(this, "B");
}
}
class Component2 : BaseComponent
{
public void DoC()
{
Console.WriteLine("Component 2 does C.");
this._mediator.Notify(this, "C");
}
public void DoD()
{
Console.WriteLine("Component 2 does D.");
this._mediator.Notify(this, "D");
}
}
class Program
{
static void Main(string[] args)
{
// شيفرة العميل.
Component1 component1 = new Component1();
Component2 component2 = new Component2();
new ConcreteMediator(component1, component2);
Console.WriteLine("Client triggets operation A.");
component1.DoA();
Console.WriteLine();
Console.WriteLine("Client triggers operation D.");
component2.DoD();
}
}
}
Output.txt: نتائج التنفيذ
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.
Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.
الاستخدام في لغة PHP
المستوى: ★ ★ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: لا يكثر استخدام نمط الوسيط بشكل خالص في لغة PHP على خلاف اللغات الأخرى، خاصة تلك التي تستهدف الواجهات الرسومية مثل جافا أو #C، فقد يحتوي تطبيق PHP على عشرات العناصر، لكنها نادرًا ما تتواصل مباشرة داخل جلسة واحدة.
لكن رغم هذا، لا تزال لدينا استخدامات لنمط الوسيط مثل مرسِلات الأحداث (event dispatchers) للعديد من أطر العمل في PHP، أو بعض التطبيقات لمتحكمات MVC.
مثال تصوري
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
سيكون من السهل عليك استيعاب المثال التالي بعد تعلم بنية النمط، بناء على استخدام واقعي له في لغة PHP.
index.php: مثال تصوري
<?php
namespace RefactoringGuru\Mediator\Conceptual;
/**
* تصرِّح واجهة الوسيط عن أسلوب تستخدمه العناصر لإشعار الوسيط
* بالأحداث المختلفة، وقد يتفاعل الوسيط مع هذه الأحداث ويمرر التنفيذ إلى
* عناصر أخرى.
*/
interface Mediator
{
public function notify(object $sender, string $event): void;
}
/**
* سلوكًا تعاونيًا من خلال mediators يستخدم الوسطاء
* التنسيق بين عدة عناصر.
*/
class ConcreteMediator implements Mediator
{
private $component1;
private $component2;
public function __construct(Component1 $c1, Component2 $c2)
{
$this->component1 = $c1;
$this->component1->setMediator($this);
$this->component2 = $c2;
$this->component2->setMediator($this);
}
public function notify(object $sender, string $event): void
{
if ($event == "A") {
echo "Mediator reacts on A and triggers following operations:\n";
$this->component2->doC();
}
if ($event == "D") {
echo "Mediator reacts on D and triggers following operations:\n";
$this->component1->doB();
$this->component2->doC();
}
}
}
/**
* الوظيفية الأساسية لتخزين نسخة من الوسيط Base Component يوفر العنصر الأساسي
* داخل كائنات العنصر.
*/
class BaseComponent
{
protected $mediator;
public function __construct(Mediator $mediator = null)
{
$this->mediator = $mediator;
}
public function setMediator(Mediator $mediator): void
{
$this->mediator = $mediator;
}
}
/**
* متعددة functionalities تستخدم العناصر الحقيقية وظيفيات.
* ولا تعتمد هذه العناصر على عناصر غيرها ولا على أي فئة وسيط حقيقية.
*/
class Component1 extends BaseComponent
{
public function doA(): void
{
echo "Component 1 does A.\n";
$this->mediator->notify($this, "A");
}
public function doB(): void
{
echo "Component 1 does B.\n";
$this->mediator->notify($this, "B");
}
}
class Component2 extends BaseComponent
{
public function doC(): void
{
echo "Component 2 does C.\n";
$this->mediator->notify($this, "C");
}
public function doD(): void
{
echo "Component 2 does D.\n";
$this->mediator->notify($this, "D");
}
}
/**
* شيفرة العميل
*/
$c1 = new Component1;
$c2 = new Component2;
$mediator = new ConcreteMediator($c1, $c2);
echo "Client triggers operation A.\n";
$c1->doA();
echo "\n";
echo "Client triggers operation D.\n";
$c2->doD();
Output.txt: نتائج التنفيذ
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.
Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.
مثال واقعي
في هذا المثال، يوضح نمط الوسيط فكرة نمط المراقِب (Observer) من خلال توفير مرسل أحداث مركزي، سامحًا لأي كائن أن يتعقب ويبدأ الأحداث في كائنات أخرى دون الاعتماد على فئاتها.
index.php: مثال واقعي
<?php
namespace RefactoringGuru\Mediator\RealWorld;
/**
* تتصرف فئة مرسل الحدث كوسيط وتحتوي منطق الاشتراك والإشعار،
* وترتبط بواجهاتها المجردة فقط
* على الرغم من اعتماد الوسيط التقليدي في العادة على فئات العنصر الحقيقية.
*
* ونحن نستطيع تحقيق هذا المستوى من المراوغة بفضل الطريقة التي تُنشأ بها
* الاتصالات بين العناصر، ذلك أن العناصر نفسها قد تشترك في
* أحداث معينة تريدها من خلال واجهة الاشتراك للوسيط.
*
* PHP المضمنة في لغة Subject/Observer لاحظ أننا لا نستطيع استخدام واجهات
* لأننا بذلك سنكون حرفنا هذه الواجهات بعيدًا جدًا عن الغرض المصممة له.
*/
class EventDispatcher
{
/**
* @var array
*/
private $observers = [];
public function __construct()
// مجموعة الأحداث الخاصة للمراقبين الذين يريدون الاستماع للأحداث كلها.
$this->observers["*"] = [];
}
private function initEventGroup(string &$event = "*"): void
{
if (!isset($this->observers[$event])) {
$this->observers[$event] = [];
}
}
private function getEventObservers(string $event = "*"): array
{
$this->initEventGroup($event);
$group = $this->observers[$event];
$all = $this->observers["*"];
return array_merge($group, $all);
}
public function attach(Observer $observer, string $event = "*"): void
{
$this->initEventGroup($event);
$this->observers[$event][] = $observer;
}
public function detach(Observer $observer, string $event = "*"): void
{
foreach ($this->getEventObservers($event) as $key => $s) {
if ($s === $observer) {
unset($this->observers[$event][$key]);
}
}
}
public function trigger(string $event, object $emitter, $data = null): void
{
echo "EventDispatcher: Broadcasting the '$event' event.\n";
foreach ($this->getEventObservers($event) as $observer) {
$observer->update($event, $emitter, $data);
}
}
}
/**
* دالة مساعد بسيطة لتوفير وصول عام لمرسل الأحداث.
*/
function events(): EventDispatcher
{
static $eventDispatcher;
if (!$eventDispatcher) {
$eventDispatcher = new EventDispatcher;
}
return $eventDispatcher;
}
/**
* تحدد واجهة المراقب كيف تستقبل العناصر إشعارات الحدث.
*/
interface Observer
{
public function update(string $event, object $emitter, $data = null);
}
/**
* يتصرف كعنصر عادي UserRepository على عكس مثال نمط المراقب، فإن هذا المثال يجعل
* لا يملك أي أساليب تتعلق بالأحداث. وتعتمد هذه الفئة
* من أجل بث أحداثها والاستماع إلى أحداث EventDispatcher كأي عنصر آخر على
* غيرها من الفئات.
* @see \RefactoringGuru\Observer\RealWorld\UserRepository
*/
class UserRepository implements Observer
{
/**
* @var array قائمة بمستخدمي التطبيق.
*/
private $users = [];
/**
* بإمكان العناصر أن تشترك في الأحداث بنفسها أو من خلال شيفرة العميل.
*/
public function __construct()
{
events()->attach($this, "users:deleted");
}
/**
* تستطيع العناصر أن تقرر ما إن كانت تريد معالجة الحدث مستخدمة اسمه
* أو أي بيانات أخرى مُرِّرَت مع الحدث emitter أو المرسل.
*/
public function update(string $event, object $emitter, $data = null): void
{
switch ($event) {
case "users:deleted":
if ($emitter === $this) {
return;
}
$this->deleteUser($data, true);
break;
}
}
// تمثل هذه الأساليب منطق العمل الفئة.
public function initialize(string $filename): void
{
echo "UserRepository: Loading user records from a file.\n";
// ...
events()->trigger("users:init", $this, $filename);
}
public function createUser(array $data, bool $silent = false): User
{
echo "UserRepository: Creating a user.\n";
$user = new User;
$user->update($data);
$id = bin2hex(openssl_random_pseudo_bytes(16));
$user->update(["id" => $id]);
$this->users[$id] = $user;
if (!$silent) {
events()->trigger("users:created", $this, $user);
}
return $user;
}
public function updateUser(User $user, array $data, bool $silent = false): User
{
echo "UserRepository: Updating a user.\n";
$id = $user->attributes["id"];
if (!isset($this->users[$id])) {
return null;
}
$user = $this->users[$id];
$user->update($data);
if (!$silent) {
events()->trigger("users:updated", $this, $user);
}
return $user;
}
public function deleteUser(User $user, bool $silent = false): void
{
echo "UserRepository: Deleting a user.\n";
$id = $user->attributes["id"];
if (!isset($this->users[$id])) {
return;
}
unset($this->users[$id]);
if (!$silent) {
events()->trigger("users:deleted", $this, $user);
}
}
}
/**
* سنجعل فئة المستخدم مهمشة وغير مهمة بما أنها ليست الهدف من مثالنا.
*/
class User
{
public $attributes = [];
public function update($data): void
{
$this->attributes = array_merge($this->attributes, $data);
}
/**
* تستطيع كل الكائنات أن تبدأ أحداثًا.
*/
public function delete(): void
{
echo "User: I can now delete myself without worrying about the repository.\n";
events()->trigger("users:deleted", $this, $this);
}
}
/**
* يسجل هذا العنصر الحقيقي أي أحداث يكون مشتركًا فيها.
*/
class Logger implements Observer
{
private $filename;
public function __construct($filename)
{
$this->filename = $filename;
if (file_exists($this->filename)) {
unlink($this->filename);
}
}
public function update(string $event, object $emitter, $data = null)
{
$entry = date("Y-m-d H:i:s") . ": '$event' with data '" . json_encode($data) . "'\n";
file_put_contents($this->filename, $entry, FILE_APPEND);
echo "Logger: I've written '$event' entry to the log.\n";
}
}
/**
* يرسل هذا العنصر الحقيقي إرشادات مبدئية للمستخدمين الجدد، ويكون العميل
* مسؤولًا عن إرفاق هذا العنصر في حدث إنشاء مستخدم مناسب.
*/
class OnboardingNotification implements Observer
{
private $adminEmail;
public function __construct(string $adminEmail)
{
$this->adminEmail = $adminEmail;
}
public function update(string $event, object $emitter, $data = null): void
{
// mail($this->adminEmail,
// "Onboarding required",
// "We have a new user. Here's his info: " .json_encode($data));
echo "OnboardingNotification: The notification has been emailed!\n";
}
}
/**
* شيفرة العميل.
*/
$repository = new UserRepository;
events()->attach($repository, "facebook:update");
$logger = new Logger(__DIR__ . "/log.txt");
events()->attach($logger, "*");
$onboarding = new OnboardingNotification("1@example.com");
events()->attach($onboarding, "users:created");
// ...
$repository->initialize(__DIR__ . "users.csv");
// ...
$user = $repository->createUser([
"name" => "John Smith",
"email" => "john99@example.com",
]);
// ...
$user->delete();
Output.txt: نتائج التنفيذ
UserRepository: Loading user records from a file.
EventDispatcher: Broadcasting the 'users:init' event.
Logger: I've written 'users:init' entry to the log.
UserRepository: Creating a user.
EventDispatcher: Broadcasting the 'users:created' event.
OnboardingNotification: The notification has been emailed!
Logger: I've written 'users:created' entry to the log.
User: I can now delete myself without worrying about the repository.
EventDispatcher: Broadcasting the 'users:deleted' event.
UserRepository: Deleting a user.
Logger: I've written 'users:deleted' entry to the log.
الاستخدام في لغة بايثون
المستوى: ★ ★ ☆
الانتشار: ☆ ☆ ☆
أمثلة الاستخدام: لعل تسهيل التواصل بين عناصر الواجهة الرسومية لبرنامج ما هو أكثر مثال منتشر لنمط الوسيط في لغة بايثون، ويكون مرادف الوسيط هو الجزء المتحكم في نمط MVC.
مثال تصوري
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
main.py: مثال تصوري
from __future__ import annotations
from abc import ABC
class Mediator(ABC):
"""
تصرِّح واجهة الوسيط عن أسلوب تستخدمه العناصر لإشعار الوسيط
بالأحداث المختلفة، وقد يتفاعل الوسيط مع هذه الأحداث ويمرر التنفيذ إلى
عناصر أخرى.
"""
def notify(self, sender: object, event: str) -> None:
pass
class ConcreteMediator(Mediator):
def __init__(self, component1: Component1, component2: Component2) -> None:
self._component1 = component1
self._component1.mediator = self
self._component2 = component2
self._component2.mediator = self
def notify(self, sender: object, event: str) -> None:
if event == "A":
print("Mediator reacts on A and triggers following operations:")
self._component2.do_c()
elif event == "D":
print("Mediator reacts on D and triggers following operations:")
self._component1.do_b()
self._component2.do_c()
class BaseComponent:
"""
الوظيفية الأساسية لتخزين نسخة من الوسيط Base Component يوفر العنصر الأساسي
داخل كائنات العنصر.
"""
def __init__(self, mediator: Mediator = None) -> None:
self._mediator = mediator
@property
def mediator(self) -> Mediator:
return self._mediator
@mediator.setter
def mediator(self, mediator: Mediator) -> None:
self._mediator = mediator
"""
متعددة functionalities تستخدم العناصر الحقيقية وظيفيات.
ولا تعتمد هذه العناصر على عناصر غيرها ولا على أي فئة وسيط حقيقية.
"""
class Component1(BaseComponent):
def do_a(self) -> None:
print("Component 1 does A.")
self.mediator.notify(self, "A")
def do_b(self) -> None:
print("Component 1 does B.")
self.mediator.notify(self, "B")
class Component2(BaseComponent):
def do_c(self) -> None:
print("Component 2 does C.")
self.mediator.notify(self, "C")
def do_d(self) -> None:
print("Component 2 does D.")
self.mediator.notify(self, "D")
if __name__ == "__main__":
# The client code.
c1 = Component1()
c2 = Component2()
mediator = ConcreteMediator(c1, c2)
print("Client triggers operation A.")
c1.do_a()
print("\n", end="")
print("Client triggers operation D.")
c2.do_d()
Output.txt: نتائج التنفيذ
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.
Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.
الاستخدام في لغة روبي
المستوى: ★ ★ ☆
الانتشار: ☆ ☆ ☆
أمثلة الاستخدام: لعل تسهيل التواصل بين عناصر الواجهة الرسومية لبرنامج ما هو أكثر مثال منتشر لنمط الوسيط في لغة روبي، ويكون مرادف الوسيط هو الجزء المتحكم في نمط MVC.
مثال تصوري
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
main.rb: مثال تصوري
# تصرِّح واجهة الوسيط عن أسلوب تستخدمه العناصر لإشعار الوسيط
# بالأحداث المختلفة، وقد يتفاعل الوسيط مع هذه الأحداث ويمرر التنفيذ إلى
# عناصر أخرى.
class Mediator
# @abstract
#
# @param [Object] sender
# @param [String] event
def notify(_sender, _event)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end
class ConcreteMediator < Mediator
# @param [Component1] component1
# @param [Component2] component2
def initialize(component1, component2)
@component1 = component1
@component1.mediator = self
@component2 = component2
@component2.mediator = self
end
# @param [Object] sender
# @param [String] event
def notify(_sender, event)
if event == 'A'
puts 'Mediator reacts on A and triggers following operations:'
@component2.do_c
elsif event == 'D'
puts 'Mediator reacts on D and triggers following operations:'
@component1.do_b
@component2.do_c
end
end
end
# الوظيفية الأساسية لتخزين نسخة من الوسيط Base Component يوفر العنصر الأساسي
# داخل كائنات العنصر.
class BaseComponent
# @return [Mediator]
attr_accessor :mediator
# @param [Mediator] mediator
def initialize(mediator = nil)
@mediator = mediator
end
end
# متعددة functionalities تستخدم العناصر الحقيقية وظيفيات.
# ولا تعتمد هذه العناصر على عناصر غيرها ولا على أي فئة وسيط حقيقية.
class Component1 < BaseComponent
def do_a
puts 'Component 1 does A.'
@mediator.notify(self, 'A')
end
def do_b
puts 'Component 1 does B.'
@mediator.notify(self, 'B')
end
end
class Component2 < BaseComponent
def do_c
puts 'Component 2 does C.'
@mediator.notify(self, 'C')
end
def do_d
puts 'Component 2 does D.'
@mediator.notify(self, 'D')
end
end
# شيفرة العميل.
c1 = Component1.new
c2 = Component2.new
ConcreteMediator.new(c1, c2)
puts 'Client triggers operation A.'
c1.do_a
puts "\n"
puts 'Client triggers operation D.'
c2.do_d
output.txt: نتائج التنفيذ
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.
Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.
الاستخدام في لغة Swift
المستوى: ★ ★ ☆
الانتشار: ☆ ☆ ☆
أمثلة الاستخدام: لعل تسهيل التواصل بين عناصر الواجهة الرسومية لبرنامج ما هو أكثر مثال منتشر لنمط الوسيط في لغة Swift. ويكون مرادف الوسيط هو الجزء المتحكم في نمط MVC.
مثال تصوري
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
Example.swift: مثال تصوري
import XCTest
/// تصرِّح واجهة الوسيط عن أسلوب تستخدمه العناصر لإشعار الوسيط
/// بالأحداث المختلفة، وقد يتفاعل الوسيط مع هذه الأحداث ويمرر التنفيذ إلى
/// عناصر أخرى.
protocol Mediator: AnyObject {
func notify(sender: BaseComponent, event: String)
}
/// سلوكًا تعاونيًا من خلال mediators يستخدم الوسطاء
/// التنسيق بين عدة عناصر.
class ConcreteMediator: Mediator {
private var component1: Component1
private var component2: Component2
init(_ component1: Component1, _ component2: Component2) {
self.component1 = component1
self.component2 = component2
component1.update(mediator: self)
component2.update(mediator: self)
}
func notify(sender: BaseComponent, event: String) {
if event == "A" {
print("Mediator reacts on A and triggers following operations:")
self.component2.doC()
}
else if (event == "D") {
print("Mediator reacts on D and triggers following operations:")
self.component1.doB()
self.component2.doC()
}
}
}
/// الوظيفية الأساسية لتخزين نسخة من الوسيط Base Component يوفر العنصر الأساسي
/// داخل كائنات العنصر.
class BaseComponent {
fileprivate weak var mediator: Mediator?
init(mediator: Mediator? = nil) {
self.mediator = mediator
}
func update(mediator: Mediator) {
self.mediator = mediator
}
}
/// متعددة functionalities تستخدم العناصر الحقيقية وظيفيات.
/// ولا تعتمد هذه العناصر على عناصر غيرها ولا على أي فئة وسيط حقيقية.
class Component1: BaseComponent {
func doA() {
print("Component 1 does A.")
mediator?.notify(sender: self, event: "A")
}
func doB() {
print("Component 1 does B.\n")
mediator?.notify(sender: self, event: "B")
}
}
class Component2: BaseComponent {
func doC() {
print("Component 2 does C.")
mediator?.notify(sender: self, event: "C")
}
func doD() {
print("Component 2 does D.")
mediator?.notify(sender: self, event: "D")
}
}
/// لنرى كيف سيعمل كل هذا..
class MediatorConceptual: XCTestCase {
func testMediatorConceptual() {
let component1 = Component1()
let component2 = Component2()
let mediator = ConcreteMediator(component1, component2)
print("Client triggers operation A.")
component1.doA()
print("\nClient triggers operation D.")
component2.doD()
print(mediator)
}
}
Output.txt: نتائج التنفيذ
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.
Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.
مثال واقعي
Example.swift: مثال واقعي
import XCTest
class MediatorRealWorld: XCTestCase {
func test() {
let newsArray = [News(id: 1, title: "News1", likesCount: 1),
News(id: 2, title: "News2", likesCount: 2)]
let numberOfGivenLikes = newsArray.reduce(0, { $0 + $1.likesCount })
let mediator = ScreenMediator()
let feedVC = NewsFeedViewController(mediator, newsArray)
let newsDetailVC = NewsDetailViewController(mediator, newsArray.first!)
let profileVC = ProfileViewController(mediator, numberOfGivenLikes)
mediator.update([feedVC, newsDetailVC, profileVC])
feedVC.userLikedAllNews()
feedVC.userDislikedAllNews()
}
}
class NewsFeedViewController: ScreenUpdatable {
private var newsArray: [News]
private weak var mediator: ScreenUpdatable?
init(_ mediator: ScreenUpdatable?, _ newsArray: [News]) {
self.newsArray = newsArray
self.mediator = mediator
}
func likeAdded(to news: News) {
print("News Feed: Received a liked news model with id \(news.id)")
for var item in newsArray {
if item == news {
item.likesCount += 1
}
}
}
func likeRemoved(from news: News) {
print("News Feed: Received a disliked news model with id \(news.id)")
for var item in newsArray {
if item == news {
item.likesCount -= 1
}
}
}
func userLikedAllNews() {
print("\n\nNews Feed: User LIKED all news models")
print("News Feed: I am telling to mediator about it...\n")
newsArray.forEach({ mediator?.likeAdded(to: $0) })
}
func userDislikedAllNews() {
print("\n\nNews Feed: User DISLIKED all news models")
print("News Feed: I am telling to mediator about it...\n")
newsArray.forEach({ mediator?.likeRemoved(from: $0) })
}
}
class NewsDetailViewController: ScreenUpdatable {
private var news: News
private weak var mediator: ScreenUpdatable?
init(_ mediator: ScreenUpdatable?, _ news: News) {
self.news = news
self.mediator = mediator
}
func likeAdded(to news: News) {
print("News Detail: Received a liked news model with id \(news.id)")
if self.news == news {
self.news.likesCount += 1
}
}
func likeRemoved(from news: News) {
print("News Detail: Received a disliked news model with id \(news.id)")
if self.news == news {
self.news.likesCount -= 1
}
}
}
class ProfileViewController: ScreenUpdatable {
private var numberOfGivenLikes: Int
private weak var mediator: ScreenUpdatable?
init(_ mediator: ScreenUpdatable?, _ numberOfGivenLikes: Int) {
self.numberOfGivenLikes = numberOfGivenLikes
self.mediator = mediator
}
func likeAdded(to news: News) {
print("Profile: Received a liked news model with id \(news.id)")
numberOfGivenLikes += 1
}
func likeRemoved(from news: News) {
print("Profile: Received a disliked news model with id \(news.id)")
numberOfGivenLikes -= 1
}
}
protocol ScreenUpdatable: class {
func likeAdded(to news: News)
func likeRemoved(from news: News)
}
class ScreenMediator: ScreenUpdatable {
private var screens: [ScreenUpdatable]?
func update(_ screens: [ScreenUpdatable]) {
self.screens = screens
}
func likeAdded(to news: News) {
print("Screen Mediator: Received a liked news model with id \(news.id)")
screens?.forEach({ $0.likeAdded(to: news) })
}
func likeRemoved(from news: News) {
print("ScreenMediator: Received a disliked news model with id \(news.id)")
screens?.forEach({ $0.likeRemoved(from: news) })
}
}
struct News: Equatable {
let id: Int
let title: String
var likesCount: Int
/// خصائص أخرى.
static func == (left: News, right: News) -> Bool {
return left.id == right.id
}
}
Output.txt: نتائج التنفيذ
News Feed: User LIKED all news models
News Feed: I am telling to mediator about it...
Screen Mediator: Received a liked news model with id 1
News Feed: Received a liked news model with id 1
News Detail: Received a liked news model with id 1
Profile: Received a liked news model with id 1
Screen Mediator: Received a liked news model with id 2
News Feed: Received a liked news model with id 2
News Detail: Received a liked news model with id 2
Profile: Received a liked news model with id 2
News Feed: User DISLIKED all news models
News Feed: I am telling to mediator about it...
ScreenMediator: Received a disliked news model with id 1
News Feed: Received a disliked news model with id 1
News Detail: Received a disliked news model with id 1
Profile: Received a disliked news model with id 1
ScreenMediator: Received a disliked news model with id 2
News Feed: Received a disliked news model with id 2
News Detail: Received a disliked news model with id 2
Profile: Received a disliked news model with id 2
الاستخدام في لغة TypeScript
المستوى: ★ ★ ☆
الانتشار: ☆ ☆ ☆
أمثلة الاستخدام: لعل تسهيل التواصل بين عناصر الواجهة الرسومية لبرنامج ما هو أكثر مثال منتشر لنمط الوسيط في لغة TypeScript، ويكون مرادف الوسيط هو الجزء المتحكم في نمط MVC.
مثال تصوري
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
index.ts: مثال تصوري
/**
* تصرِّح واجهة الوسيط عن أسلوب تستخدمه العناصر لإشعار الوسيط
* بالأحداث المختلفة، وقد يتفاعل الوسيط مع هذه الأحداث ويمرر التنفيذ إلى
* عناصر أخرى.
*/
interface Mediator {
notify(sender: object, event: string): void;
}
/**
* سلوكًا تعاونيًا من خلال mediators يستخدم الوسطاء
* التنسيق بين عدة عناصر.
*/
class ConcreteMediator implements Mediator {
private component1: Component1;
private component2: Component2;
constructor(c1: Component1, c2: Component2) {
this.component1 = c1;
this.component1.setMediator(this);
this.component2 = c2;
this.component2.setMediator(this);
}
public notify(sender: object, event: string): void {
if (event === 'A') {
console.log('Mediator reacts on A and triggers following operations:');
this.component2.doC();
}
if (event === 'D') {
console.log('Mediator reacts on D and triggers following operations:');
this.component1.doB();
this.component2.doC();
}
}
}
/**
* الوظيفية الأساسية لتخزين نسخة من الوسيط Base Component يوفر العنصر الأساسي
* داخل كائنات العنصر.
*/
class BaseComponent {
protected mediator: Mediator;
constructor(mediator: Mediator = null) {
this.mediator = mediator;
}
public setMediator(mediator: Mediator): void {
this.mediator = mediator;
}
}
/**
* متعددة functionalities تستخدم العناصر الحقيقية وظيفيات.
* ولا تعتمد هذه العناصر على عناصر غيرها ولا على أي فئة وسيط حقيقية.
*/
class Component1 extends BaseComponent {
public doA(): void {
console.log('Component 1 does A.');
this.mediator.notify(this, 'A');
}
public doB(): void {
console.log('Component 1 does B.');
this.mediator.notify(this, 'B');
}
}
class Component2 extends BaseComponent {
public doC(): void {
console.log('Component 2 does C.');
this.mediator.notify(this, 'C');
}
public doD(): void {
console.log('Component 2 does D.');
this.mediator.notify(this, 'D');
}
}
/**
* شيفرة العميل.
*/
const c1 = new Component1();
const c2 = new Component2();
const mediator = new ConcreteMediator(c1, c2);
console.log('Client triggers operation A.');
c1.doA();
console.log('');
console.log('Client triggers operation D.');
c2.doD();
Output.txt: نتائج التنفيذ
Client triggers operation A.
Component 1 does A.
Mediator reacts on A and triggers following operations:
Component 2 does C.
Client triggers operation D.
Component 2 does D.
Mediator reacts on D and triggers following operations:
Component 1 does B.
Component 2 does C.