ConstructiCat Logo
CodeBust.
Browse section ▾

المُزَخرِف.

المُزَخرِف هو نمط تصميم هيكلي يتيح لك إرفاق سلوكيات جديدة بالكائنات عن طريق وضع هذه الكائنات داخل كائنات غلاف خاصة تحتوي على هذه السلوكيات.

##Intent

المُزَخرِف هو نمط تصميم هيكلي يتيح لك إرفاق سلوكيات جديدة بالكائنات عن طريق وضع هذه الكائنات داخل كائنات غلاف خاصة تحتوي على هذه السلوكيات.

Decorator design pattern

##Problem

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

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

Structure of the library before applying the Decorator pattern

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

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

Structure of the library after implementing other notification types

يُنفَّذ كل نوع إشعار كفئة فرعية من المُخطِر.

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

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

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

Structure of the library after creating class combinations

انفجار تركيبي في الفئات الفرعية.

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

##Solution

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

  • الوراثة ثابتة. لا يمكنك تعديل سلوك كائن موجود في وقت التشغيل. يمكنك فقط استبداله بكائن آخر مُنشأ من فئة فرعية مختلفة.
  • يمكن للفئات الفرعية أن يكون لها فئة أصل واحدة فقط. في أغلب اللغات، لا تسمح الوراثة لفئة بأن ترث سلوكيات فئات متعددة في آنٍ واحد.

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

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

Inheritance vs. Aggregation

الوراثة مقابل التجميع

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

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

في مثال الإشعارات لدينا، لنترك سلوك الإشعار البريدي البسيط داخل فئة Notifier الأساسية ونحوّل جميع أساليب الإشعار الأخرى إلى مزخرفات.

The solution with the Decorator pattern

Various notification methods become decorators.

سيحتاج كود العميل إلى تغليف كائن المُخطِر الأساسي في مجموعة من المزخرفات التي تتناسب مع تفضيلات العميل. ستكون الكائنات الناتجة منظمة على شكل مكدس.

Apps might configure complex stacks of notification decorators

Apps might configure complex stacks of notification decorators.

سيكون المزخرف الأخير في المكدس هو الكائن الذي يتعامل معه العميل فعليًا. بما أن جميع المزخرفات تنفّذ نفس الواجهة كالمُخطِر الأساسي، فلن يهتم بقية كود العميل بمعرفة ما إذا كان يعمل مع كائن المُخطِر “الصافي” أم المزخرَف.

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

##Structure

Client «interface» Component +execute() ConcreteComponent ...+execute() BaseDecorator -wrappee: Component+BaseDecorator(c: Component)+execute() ConcreteDecorators ...+execute()+extra() a = new ConcComponent()b = new ConcDecorator1(a)c = new ConcDecorator2(b)c.execute()// Decorator -> Decorator -> Component wrappee = c wrappee.execute() super::execute()extra()
Client «interface» Component +execute() ConcreteComponent ...+execute() BaseDecorator -wrappee: Component+BaseDecorator(c: Component)+execute() ConcreteDecorators ...+execute()+extra() a = new ConcComponent()b = new ConcDecorator1(a)c = new ConcDecorator2(b)c.execute()// Decorator -> Decorator -> Component wrappee = c wrappee.execute() super::execute()extra() 1 2 3 4 5
  1. يُعلن المكوّن عن الواجهة المشتركة لكل من الأغلفة والكائنات المُغلَّفة.

  2. المكوّن الملموس هو فئة الكائنات التي يجري تغليفها. يُعرّف السلوك الأساسي الذي يمكن للمزخرفات تعديله.

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

  4. تُعرِّف المزخرفات الملموسة سلوكيات إضافية يمكن إضافتها إلى المكوّنات ديناميكيًا. تتجاوز المزخرفات الملموسة أساليب المزخرف الأساسي وتنفّذ سلوكها قبل أو بعد استدعاء الأسلوب الأصلي.

  5. يمكن لـالعميل تغليف المكوّنات في طبقات متعددة من المزخرفات، طالما يتعامل مع جميع الكائنات عبر واجهة المكوّن.

##Pseudocode

في هذا المثال، يتيح لك نمط المُزَخرِف ضغط البيانات الحساسة وتشفيرها بصورة مستقلة عن الشيفرة التي تستخدم هذه البيانات فعليًا.

Structure of the Decorator pattern example

The encryption and compression decorators example.

يُغلّف التطبيق كائن مصدر البيانات بزوج من المزخرفات. يغيّر كلا الغلافين الطريقة التي تُكتب بها البيانات إلى القرص وتُقرأ منه:

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

  • فور قراءة البيانات من القرص، تمر عبر نفس المزخرفات التي تفكّ ضغطها وتفكّ تشفيرها.

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

// تُعرِّف واجهة المكوّن العمليات التي يمكن
// للمزخرفات تعديلها.
interface DataSource is
    method writeData(data)
    method readData():data

// توفر المكوّنات الملموسة تطبيقات افتراضية
// للعمليات. قد تكون هناك عدة متغيرات من هذه
// الفئات في البرنامج.
class FileDataSource implements DataSource is
    constructor FileDataSource(filename) { ... }

    method writeData(data) is
        // كتابة البيانات إلى الملف.

    method readData():data is
        // قراءة البيانات من الملف.

// تتبع فئة المزخرف الأساسي نفس الواجهة كبقية المكوّنات.
// الغرض الأساسي من هذه الفئة هو تعريف واجهة التغليف
// لجميع المزخرفات الملموسة. قد يتضمن التطبيق الافتراضي
// لكود التغليف حقلاً لتخزين مكوّن مُغلَّف ووسائل تهيئته.
class DataSourceDecorator implements DataSource is
    protected field wrappee: DataSource

    constructor DataSourceDecorator(source: DataSource) is
        wrappee = source

    // يفوّض المزخرف الأساسي جميع الأعمال ببساطة إلى
    // المكوّن المُغلَّف. يمكن إضافة سلوكيات إضافية في
    // المزخرفات الملموسة.
    method writeData(data) is
        wrappee.writeData(data)

    // قد تستدعي المزخرفات الملموسة التطبيق الأصلي للعملية
    // بدلاً من استدعاء الكائن المُغلَّف مباشرة. يبسّط
    // هذا النهج توسيع فئات المزخرف.
    method readData():data is
        return wrappee.readData()

// يجب أن تستدعي المزخرفات الملموسة أساليب الكائن المُغلَّف،
// لكنها قد تضيف شيئًا خاصًا بها إلى النتيجة. يمكن
// للمزخرفات تنفيذ السلوك المضاف قبل أو بعد الاستدعاء
// إلى كائن مُغلَّف.
class EncryptionDecorator extends DataSourceDecorator is
    method writeData(data) is
        // 1. تشفير البيانات الممررة.
        // 2. تمرير البيانات المُشفَّرة إلى طريقة writeData
        // الخاصة بـ wrappee.

    method readData():data is
        // 1. الحصول على البيانات من طريقة readData الخاصة بـ wrappee.
        // 2. محاولة فك تشفيرها إذا كانت مُشفَّرة.
        // 3. إرجاع النتيجة.

// يمكنك تغليف الكائنات في عدة طبقات من المزخرفات.
class CompressionDecorator extends DataSourceDecorator is
    method writeData(data) is
        // 1. ضغط البيانات الممررة.
        // 2. تمرير البيانات المضغوطة إلى طريقة writeData
        // الخاصة بـ wrappee.

    method readData():data is
        // 1. الحصول على البيانات من طريقة readData الخاصة بـ wrappee.
        // 2. محاولة فك ضغطها إذا كانت مضغوطة.
        // 3. إرجاع النتيجة.


// الخيار الأول. مثال بسيط على تجميع المزخرف.
class Application is
    method dumbUsageExample() is
        source = new FileDataSource("somefile.dat")
        source.writeData(salaryRecords)
        // تمت كتابة الملف الهدف ببيانات عادية.

        source = new CompressionDecorator(source)
        source.writeData(salaryRecords)
        // تمت كتابة الملف الهدف ببيانات
        // مضغوطة.

        source = new EncryptionDecorator(source)
        // يحتوي المتغير source الآن على هذا:
        // Encryption > Compression > FileDataSource
        source.writeData(salaryRecords)
        // تمت كتابة الملف ببيانات مضغوطة
        // ومُشفَّرة.


// الخيار الثاني. كود عميل يستخدم مصدر بيانات خارجي.
// لا تعرف كائنات SalaryManager تفاصيل التخزين ولا تكترث لها.
// تعمل مع مصدر بيانات مُهيَّأ مسبقًا تتلقاه من مُهيِّئ التطبيق.
class SalaryManager is
    field source: DataSource

    constructor SalaryManager(source: DataSource) { ... }

    method load() is
        return source.readData()

    method save() is
        source.writeData(salaryRecords)
    // ...طرق مفيدة أخرى...


// يمكن للتطبيق تجميع مكدسات مختلفة من المزخرفات
// في وقت التشغيل بحسب الإعداد أو البيئة.
class ApplicationConfigurator is
    method configurationExample() is
        source = new FileDataSource("salary.dat")
        if (enabledEncryption)
            source = new EncryptionDecorator(source)
        if (enabledCompression)
            source = new CompressionDecorator(source)

        logger = new SalaryManager(source)
        salary = logger.load()
    // ...

##Applicability

استخدم نمط المُزَخرِف عندما تحتاج إلى إسناد سلوكيات إضافية للكائنات في وقت التشغيل دون كسر الشيفرة التي تستخدم هذه الكائنات.

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

استخدم النمط عندما يكون توسيع سلوك الكائن بالوراثة أمرًا محرجًا أو غير ممكن.

تحتوي كثير من لغات البرمجة على الكلمة المفتاحية final التي يمكن استخدامها لمنع توسيع الفئة. بالنسبة للفئة النهائية، الطريقة الوحيدة لإعادة استخدام السلوك الموجود هي تغليف الفئة بغلافك الخاص باستخدام نمط المُزَخرِف.

##How to Implement

  1. تأكد من إمكانية تمثيل مجال أعمالك كمكوّن أساسي مع طبقات اختيارية متعددة فوقه.

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

  3. أنشئ فئة مكوّن ملموس وعرّف فيها السلوك الأساسي.

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

  5. تأكد من أن جميع الفئات تطبّق واجهة المكوّن.

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

  7. يجب أن يكون كود العميل مسؤولاً عن إنشاء المزخرفات وتأليفها بالطريقة التي يحتاجها العميل.

##Pros & Cons

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

##Relations with Other Patterns

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

  • مع المحوّل تصل إلى كائن موجود عبر واجهة مختلفة. مع الوكيل، تبقى الواجهة كما هي. مع المُزَخرِف تصل إلى الكائن عبر واجهة مُحسَّنة.

  • سلسلة المسؤولية والمُزَخرِف لهما بنيتا فئة متشابهتان جدًا. يعتمد كلا النمطين على التأليف التكراري لتمرير التنفيذ عبر سلسلة من الكائنات. غير أن ثمة فروقات جوهرية عدة.

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

  • المركّب والمُزَخرِف لهما مخططا بنية متشابهان إذ يعتمدان على التأليف التكراري لتنظيم عدد غير محدد من الكائنات.

    المُزَخرِف يشبه المركّب لكن له مكوّن ابن واحد فقط. وثمة فارق جوهري آخر: المُزَخرِف يضيف مسؤوليات إضافية إلى الكائن المُغلَّف، بينما يكتفي المركّب بـ“جمع” نتائج أبنائه.

    غير أن النمطين يمكنهما أيضًا التعاون: يمكنك استخدام المُزَخرِف لتوسيع سلوك كائن معين في شجرة المركّب.

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

  • المُزَخرِف يتيح لك تغيير مظهر الكائن، بينما يتيح لك الاستراتيجية تغيير جوهره.

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