ConstructiCat Logo
CodeBust.
Browse section ▾

المهايئ (Adapter).

المهايئ (Adapter) هو نمط تصميم هيكلي يسمح للكائنات ذات الواجهات غير المتوافقة بالتعاون معاً.

##Intent

المهايئ (Adapter) هو نمط تصميم هيكلي يسمح للكائنات ذات الواجهات غير المتوافقة بالتعاون معاً.

Adapter design pattern

##Problem

تخيل أنك تقوم بإنشاء تطبيق لمراقبة سوق الأسهم. يقوم التطبيق بتنزيل بيانات الأسهم من مصادر متعددة بتنسيق XML ثم يعرض مخططات ورسومات بيانية جذابة للمستخدم.

في مرحلة ما، تقرر تحسين التطبيق من خلال دمج مكتبة تحليلات ذكية تابعة لجهة خارجية. ولكن هناك مشكلة: مكتبة التحليلات تعمل فقط مع البيانات بتنسيق JSON.

The structure of the app before integration with the analytics library

لا يمكنك استخدام مكتبة التحليلات “كما هي” لأنها تتوقع البيانات بتنسيق غير متوافق مع تطبيقك.

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

##Solution

يمكنك إنشاء مهايئ. هذا كائن خاص يقوم بتحويل واجهة كائن ما بحيث يمكن لكائن آخر فهمها.

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

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

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

في بعض الأحيان يكون من الممكن إنشاء مهايئ ثنائي الاتجاه يمكنه تحويل الاستدعاءات في كلا الاتجاهين.

Adapter's solution

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

##Structure

مهايئ الكائنات

يعتمد هذا التنفيذ على مبدأ تركيب الكائنات (object composition): يقوم المهايئ بتنفيذ واجهة كائن ما ويلف الآخر. ويمكن تنفيذه في جميع لغات البرمجة الشائعة.

Structure of the Adapter design pattern (the object adapter)Structure of the Adapter design pattern (the object adapter)
  1. **العميل (Client)** هو فئة تحتوي على منطق العمل الحالي للبرنامج.

  2. **واجهة العميل (Client Interface)** تصف بروتوكولاً يجب على الفئات الأخرى اتباعه لتكون قادرة على التعاون مع كود العميل.

  3. **الخدمة (Service)** هي فئة مفيدة (عادةً ما تكون تابعة لجهة خارجية أو كود قديم). لا يمكن للعميل استخدام هذه الفئة مباشرة لأنها تحتوي على واجهة غير متوافقة.

  4. **المهايئ (Adapter)** هو فئة قادرة على العمل مع كل من العميل الخدمة: فهو ينفذ واجهة العميل، ويلف كائن الخدمة. يتلقى المهايئ استدعاءات من العميل عبر واجهة العميل ويترجمها إلى استدعاءات لكائن الخدمة الملفوف بتنسيق يمكنه فهمه.

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

مهايئ الفئات

يستخدم هذا التنفيذ الوراثة: يرث المهايئ الواجهات من كلا الكائنين في نفس الوقت. لاحظ أنه لا يمكن تنفيذ هذا النهج إلا في لغات البرمجة التي تدعم الوراثة المتعددة، مثل C++.

Adapter design pattern (class adapter)Adapter design pattern (class adapter)
  1. لا يحتاج **مهايئ الفئة (Class Adapter)** إلى تغليف أي كائنات لأنه يرث السلوكيات من كل من العميل والخدمة. يحدث التكيف داخل الدوال المعاد تعريفها (overridden methods). يمكن استخدام المهايئ الناتج بدلاً من فئة العميل الحالية.

##Pseudocode

يعتمد هذا المثال لنمط **المهايئ (Adapter)** على الصراع الكلاسيكي بين الأوتاد المربعة والثقوب المستديرة.

Structure of the Adapter pattern example

تكييف الأوتاد المربعة مع الثقوب المستديرة.

يتظاهر المهايئ بأنه وتد مستدير، بنصف قطر يساوي نصف قطر قطر المربع (بمعنى آخر، نصف قطر أصغر دائرة يمكن أن تستوعب الوتد المربع).

// لنفترض أن لديك فئتين بواجهات متوافقة:
// RoundHole و RoundPeg.
class RoundHole is
    constructor RoundHole(radius) { ... }

    method getRadius() is
        // إرجاع نصف قطر الثقب.

    method fits(peg: RoundPeg) is
        return this.getRadius() >= peg.getRadius()

class RoundPeg is
    constructor RoundPeg(radius) { ... }

    method getRadius() is
        // إرجاع نصف قطر الوتد.


// ولكن هناك فئة غير متوافقة: SquarePeg.
class SquarePeg is
    constructor SquarePeg(width) { ... }

    method getWidth() is
        // إرجاع عرض الوتد المربع.


// تتيح لك فئة المهايئ ملاءمة الأوتاد المربعة في الثقوب المستديرة.
// وهي توسع فئة RoundPeg لتمكين كائنات المهايئ من العمل كأوتاد مستديرة.
class SquarePegAdapter extends RoundPeg is
    // في الواقع، يحتوي المهايئ على نسخة من فئة SquarePeg.
    private field peg: SquarePeg

    constructor SquarePegAdapter(peg: SquarePeg) is
        this.peg = peg

    method getRadius() is
        // يتظاهر المهايئ بأنه وتد مستدير بنصف قطر يمكنه
        // استيعاب الوتد المربع الذي يلفه المهايئ بالفعل.
        return peg.getWidth() * Math.sqrt(2) / 2


// في مكان ما في كود العميل.
hole = new RoundHole(5)
rpeg = new RoundPeg(5)
hole.fits(rpeg) // true

small_sqpeg = new SquarePeg(5)
large_sqpeg = new SquarePeg(10)
hole.fits(small_sqpeg) // لن يتم تجميع هذا (أنواع غير متوافقة)

small_sqpeg_adapter = new SquarePegAdapter(small_sqpeg)
large_sqpeg_adapter = new SquarePegAdapter(large_sqpeg)
hole.fits(small_sqpeg_adapter) // true
hole.fits(large_sqpeg_adapter) // false

##Applicability

استخدم فئة المهايئ (Adapter) عندما تريد استخدام فئة موجودة بالفعل، ولكن واجهتها ليست متوافقة مع بقية الكود الخاص بك.

يتيح لك نمط المهايئ إنشاء فئة وسيطة تعمل كمترجم بين كودك وفئة قديمة أو فئة تابعة لجهة خارجية (3rd-party) أو أي فئة أخرى ذات واجهة غريبة.

استخدم هذا النمط عندما تريد إعادة استخدام العديد من الفئات الفرعية (subclasses) الحالية التي تفتقر إلى بعض الوظائف المشتركة التي لا يمكن إضافتها إلى الفئة الأساسية (superclass).

يمكنك توسيع كل فئة فرعية ووضع الوظائف المفقودة في فئات فرعية جديدة. ومع ذلك، ستحتاج إلى تكرار الكود عبر جميع هذه الفئات الجديدة، وهو أمر سيء للغاية.

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

##How to Implement

  1. تأكد من أن لديك فئتين على الأقل بواجهات غير متوافقة:

    • فئة خدمة مفيدة لا يمكنك تغييرها (غالباً ما تكون تابعة لجهة خارجية، أو كود قديم، أو تحتوي على الكثير من الاعتماديات الحالية).
    • فئة عميل واحدة أو أكثر ستستفيد من استخدام فئة الخدمة.
  2. أعلن عن واجهة العميل ووصف كيف يتواصل العملاء مع الخدمة.

  3. أنشئ فئة المهايئ واجعلها تتبع واجهة العميل. اترك جميع الدوال فارغة في الوقت الحالي.

  4. أضف حقلاً إلى فئة المهايئ لتخزين مرجع لكائن الخدمة. الممارسة الشائعة هي تهيئة هذا الحقل عبر المنشئ (constructor)، ولكن في بعض الأحيان يكون من الأنسب تمريره إلى المهايئ عند استدعاء دواله.

  5. واحدة تلو الأخرى، قم بتنفيذ جميع دوال واجهة العميل في فئة المهايئ. يجب على المهايئ تفويض معظم العمل الفعلي لكائن الخدمة، مع معالجة الواجهة أو تحويل تنسيق البيانات فقط.

  6. يجب على العملاء استخدام المهايئ عبر واجهة العميل. سيتيح لك ذلك تغيير المهايئات أو توسيعها دون التأثير على كود العميل.

##Pros & Cons

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

##Relations with Other Patterns

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

  • يوفر المهايئ (Adapter) واجهة مختلفة تماماً للوصول إلى كائن موجود. من ناحية أخرى، مع نمط المزخرف (Decorator) تظل الواجهة كما هي أو يتم توسيعها. بالإضافة إلى ذلك، يدعم المزخرف التركيب العودي، وهو أمر غير ممكن عند استخدام المهايئ.

  • باستخدام المهايئ (Adapter) يمكنك الوصول إلى كائن موجود عبر واجهة مختلفة. باستخدام الوكيل (Proxy)، تظل الواجهة كما هي. باستخدام المزخرف (Decorator) يمكنك الوصول إلى الكائن عبر واجهة محسنة.

  • الواجهة (Facade) يعرّف واجهة جديدة للكائنات الموجودة، بينما يحاول المهايئ (Adapter) جعل الواجهة الحالية قابلة للاستخدام. يلف _المهايئ_ عادةً كائناً واحداً فقط، بينما يعمل _الواجهة_ مع نظام فرعي كامل من الكائنات.

  • تمتلك أنماط الجسر (Bridge)، الحالة (State)، الاستراتيجية (Strategy) (وإلى حد ما المهايئ (Adapter)) هياكل متشابهة جداً. في الواقع، تعتمد جميع هذه الأنماط على التركيب (composition)، وهو تفويض العمل لكائنات أخرى. ومع ذلك، فإنها جميعاً تحل مشكلات مختلفة. النمط ليس مجرد وصفة لهيكلة الكود الخاص بك بطريقة معينة. يمكنه أيضاً إبلاغ المطورين الآخرين بالمشكلة التي يحلها النمط.