ConstructiCat Logo
CodeBust.
Browse section ▾

الوسيط.

الوسيط هو نمط تصميم سلوكي يُتيح لك تقليل الاعتماديات الفوضوية بين الكائنات. يقيّد النمط الاتصالات المباشرة بين الكائنات ويُجبرها على التعاون فقط عبر كائن وسيط.

##Intent

الوسيط هو نمط تصميم سلوكي يُتيح لك تقليل الاعتماديات الفوضوية بين الكائنات. يقيّد النمط الاتصالات المباشرة بين الكائنات ويُجبرها على التعاون فقط عبر كائن وسيط.

نمط تصميم الوسيط

##Problem

لنفترض أن لديك مربع حوار لإنشاء ملفات تعريف العملاء وتحريرها. يتألف من عناصر تحكم متنوعة في النموذج كحقول النصوص ومربعات الاختيار والأزرار وغيرها.

علاقات فوضوية بين عناصر واجهة المستخدم

قد تصبح العلاقات بين عناصر واجهة المستخدم فوضوية مع تطور التطبيق.

قد تتفاعل بعض عناصر النموذج مع عناصر أخرى. على سبيل المثال، قد يؤدي تحديد مربع الاختيار "لدي كلب" إلى إظهار حقل نص مخفي لإدخال اسم الكلب. مثال آخر هو زر الإرسال الذي يجب أن يتحقق من قيم جميع الحقول قبل حفظ البيانات.

عناصر واجهة المستخدم مترابطة

قد يكون للعناصر علاقات كثيرة مع عناصر أخرى. وبالتالي، قد تؤثر التغييرات في بعض العناصر على الأخرى.

بتطبيق هذا المنطق مباشرة داخل كود عناصر النموذج، تجعل فئات هذه العناصر أصعب بكثير في إعادة الاستخدام في نماذج أخرى من التطبيق. على سبيل المثال، لن تتمكن من استخدام فئة مربع الاختيار ذلك في نموذج آخر لأنها مرتبطة بحقل نص الكلب. يمكنك استخدام إما جميع الفئات المشاركة في تصيير نموذج الملف الشخصي، أو لا شيء منها على الإطلاق.

##Solution

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

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

يجب أن تتواصل عناصر واجهة المستخدم عبر الوسيط.

يجب أن تتواصل عناصر واجهة المستخدم بشكل غير مباشر، عبر كائن الوسيط.

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

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

بهذه الطريقة، يتيح لك نمط الوسيط تغليف شبكة معقدة من العلاقات بين الكائنات المختلفة داخل كائن وسيط واحد. كلما كانت تبعيات الفئة أقل، أصبح تعديلها وتوسيعها وإعادة استخدامها أسهل.

##Structure

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

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

  3. الوسطاء الملموسون يُغلّفون العلاقات بين المكوّنات المختلفة. غالباً ما يحتفظ الوسطاء الملموسون بمراجع لجميع المكوّنات التي يديرونها وأحياناً يديرون دورة حياتها أيضاً.

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

    من منظور المكوّن، يبدو كل شيء كصندوق أسود تام. المُرسِل لا يعرف من سينتهي بمعالجة طلبه، والمستقبِل لا يعرف من أرسل الطلب في الأصل.

##Pseudocode

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

Structure of the Mediator pattern example

هيكل فئات مربع حوار واجهة المستخدم.

An element, triggered by a user, doesn’t communicate with other elements directly, even if it looks like it’s supposed to. Instead, the element only needs to let its mediator know about the event, passing any contextual info along with that notification.

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

// تُعلن واجهة الوسيط عن طريقة تستخدمها المكوّنات
// لإخطار الوسيط بأحداث متنوعة. قد يستجيب الوسيط
// لهذه الأحداث ويمرر التنفيذ إلى مكوّنات أخرى.
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,
                  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")
    // ...

##Applicability

استخدم نمط الوسيط عندما يصعب تغيير بعض الفئات لأنها مرتبطة ارتباطاً وثيقاً بعدد من الفئات الأخرى.

يتيح لك النمط استخراج جميع العلاقات بين الفئات في فئة منفصلة، مما يعزل أي تغييرات على مكوّن محدد عن بقية المكوّنات.

استخدم النمط عندما لا تستطيع إعادة استخدام مكوّن في برنامج آخر لأنه يعتمد بشكل مفرط على مكوّنات أخرى.

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

استخدم الوسيط عندما تجد نفسك تُنشئ الكثير من الفئات الفرعية للمكوّنات فقط لإعادة استخدام سلوك أساسي في سياقات متنوعة.

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

##How to Implement

  1. حدّد مجموعة من الفئات المترابطة بإحكام والتي ستستفيد من مزيد من الاستقلالية (مثلاً، لتسهيل الصيانة أو إعادة الاستخدام).

  2. أعلن عن واجهة الوسيط وصف بروتوكول الاتصال المطلوب بين الوسطاء والمكوّنات المختلفة. في معظم الحالات، يكفي طريقة واحدة لاستقبال الإشعارات من المكوّنات.

    هذه الواجهة ضرورية عندما تريد إعادة استخدام فئات المكوّنات في سياقات مختلفة. طالما يعمل المكوّن مع وسيطه عبر الواجهة العامة، يمكنك ربط المكوّن بتنفيذ مختلف للوسيط.

  3. نفّذ فئة الوسيط الملموس. فكّر في تخزين مراجع لجميع المكوّنات داخل الوسيط. بهذه الطريقة، يمكنك استدعاء أي مكوّن من طرق الوسيط.

  4. يمكنك أن تذهب أبعد من ذلك وتجعل الوسيط مسؤولاً عن إنشاء وإتلاف كائنات المكوّنات. بعد ذلك، قد يشبه الوسيط مصنعاً أو واجهةً.

  5. يجب أن تخزّن المكوّنات مرجعاً لكائن الوسيط. تُنشأ الاتصال عادةً في منشئ المكوّن، حيث يُمرَّر كائن الوسيط كوسيط.

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

##Pros & Cons

Pros
  • مبدأ المسؤولية الواحدة. يمكنك استخراج الاتصالات بين المكوّنات المختلفة في مكان واحد، مما يجعل الأمر أسهل للفهم والصيانة.
  • مبدأ الفتح/الإغلاق. يمكنك تقديم وسطاء جدد دون الحاجة إلى تغيير المكوّنات الفعلية.
  • يمكنك تقليل الاقتران بين المكوّنات المختلفة للبرنامج.
  • يمكنك إعادة استخدام المكوّنات الفردية بشكل أسهل.
Cons
  • بمرور الوقت، قد يتطور الوسيط إلى كائن إله.

##Relations with Other Patterns

  • سلسلة المسؤولية، الأمر، الوسيط والمراقب تُعالج طرقاً متنوعة لربط مُرسِلي الطلبات ومستقبليها:

    • سلسلة المسؤولية تمرر طلباً بشكل تسلسلي على طول سلسلة ديناميكية من المستقبلين المحتملين حتى يتعامل أحدهم معه.
    • الأمر يُنشئ اتصالات أحادية الاتجاه بين المُرسِلين والمستقبلين.
    • الوسيط يُلغي الاتصالات المباشرة بين المُرسِلين والمستقبلين، مُجبراً إياهم على التواصل بشكل غير مباشر عبر كائن وسيط.
    • المراقب يسمح للمستقبلين بالاشتراك وإلغاء الاشتراك ديناميكياً في استلام الطلبات.
  • الواجهة والوسيط لهما وظائف مشابهة: يحاولان تنظيم التعاون بين الكثير من الفئات المترابطة بإحكام.

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

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

    هناك تطبيق شائع لنمط الوسيط يعتمد على المراقب. يلعب كائن الوسيط دور الناشر، وتعمل المكوّنات كمشتركين يشتركون في أحداث الوسيط ويلغون اشتراكهم منها. عندما يُطبَّق الوسيط بهذه الطريقة، قد يبدو مشابهاً جداً لـالمراقب.

    عندما تشعر بالالتباس، تذكّر أنه يمكنك تطبيق نمط الوسيط بطرق أخرى. على سبيل المثال، يمكنك ربط جميع المكوّنات دائماً بكائن الوسيط نفسه. هذا التطبيق لن يشبه المراقب لكنه سيظل مثيلاً من نمط الوسيط.

    الآن تخيّل برنامجاً أصبحت فيه جميع المكوّنات ناشرين، مما يسمح باتصالات ديناميكية بينهم. لن يكون هناك كائن وسيط مركزي، بل مجموعة موزّعة من المراقبين.