المحتوى:
- أنماط التصميم الإنشائية”Decorator patterns”
- ما هو نمط التصميم الإنشائي”Decorator design pattern”
- مشكلة يمكن حلها باستخدام نمط التصميم”Decorator design pattern”
- حل المشكلة باستخدام نمط التصميم”Decorator design pattern”
- بناء نمط التصميم الإنشائي”Decorator design pattern”
- تمثيل نمط التصميم”Decorator design pattern”بشكل كود
- قابلية نمط التصميم”Decorator design pattern”للتطبيق
- كيفية تنفيذ نمط التصميم”Decorator design pattern”
- إيجابيات وسلبيات نمط التصميم”Decorator design pattern”
- علاقات نمط التصميم”Decorator design pattern”مع الأنماط الأخرى
- الخاتمة
- المراجع
- أنماط التصميم الإنشائية”Decorator patterns”
الأنماط الهيكلية هي فئة من أنماط التصميم في هندسة البرمجيات. تركِّز هذه الأنماط على تنظيم الفئات والكائنات المختلفة لتكوين هياكل أكبر وتوفير العلاقات فيما بينها وتهتم الأنماط الهيكلية في المقام الأول بتكوين الفئة وتكوين الكائن. نفكر في كيفية دمج الميراث المتعدد بين فئتين أو أكثر في فئة واحدة. والنتيجة هي فئة تجمع بين خصائص الفئات الأصلية وهناك موضوعان متكرران في هذا النمط:
يُعدُّ هذا النمط مفيداً بشكل خاص لجعل مكتبات الفئات المطورة بشكل مستقل تعمل معاً.
وتصف أنماط التصميم الهيكلي طرقاً لتكوين الكائنات لتحقيق وظائف جديدة. تأتي المرونة الإضافية لتكوين الكائن من القدرة على تغيير التكوين في وقت التشغيل، وهو أمر مستحيل مع تكوين الفئة الثابتة. وسنرى مثال لأنماط التصميم الإنشائية:
لنأخذ على سبيل المثال، محرر رسم يتيح للمستخدمين رسم وترتيب العناصر الرسومية (الخطوط والمضلعات والنص وما إلى ذلك) في الصور والرسوم البيانية. التجريد الرئيسي لمحرر الرسم هو الكائن الرسومي، الذي له شكل قابل للتحرير ويمكنه رسم نفسه. حيث يتم تعريف واجهة الكائنات الرسومية بواسطة فئة مجردة تسمى الشكل. يقوم المحرر بتعريف فئة فرعية من الشكل لكل نوع من الكائنات الرسومية: فئة Line Shape للخطوط، وفئة Polygon Shape للمضلعات، وما إلى ذلك.
- ما هو نمط التصميم الإنشائي”Decorator design pattern”
نمط التصميم (Decorator design pattern) هو نمط تصميم هيكلي في هندسة البرمجيات.يسمح بإضافة السلوك إلى الكائنات الفردية، سواء بشكل ثابت أو ديناميكي، دون التأثير على سلوك الكائنات الأخرى من نفس الفئة. وفي الأساس، هو يمكِّننا من توسيع أو تعديل سلوك الكائنات في وقت التشغيل عن طريق تغليفها بكائنات الديكور ويكون هذا النمط مفيداً عندما يكون لدينا مجموعة من الوظائف التي يمكن إضافتها أو إزالتها بشكل مستقل، أو عندما يؤدي التصنيف الفرعي إلى زيادة كبيرة في الفئات لدعم كل مجموعة من الميزات.
- مشكلة يمكن حلها باستخدام نمط التصميم”Decorator design pattern”
تخيل أنَّنا نعمل على مكتبة إشعارات تتيح للبرامج الأخرى إخطار مستخدميها بالأحداث المهمة.
واعتمدنا الإصدار الأولي من المكتبة على فئة Notifier التي تحتوي على عدد قليل من الحقول ومنشئ وطريقة إرسال واحدة. يمكن أن تقبل الطريقة وسيطة رسالة من العميل وترسل الرسالة إلى قائمة رسائل البريد الإلكتروني التي تمَّ تمريرها عبر مُنشئها. كان من المفترض أن يقوم تطبيق جهة خارجية يعمل كعميل بإنشاء كائن التنبيه وتكوينه مرة واحدة، ثم استخدامه في كل مرة يحدث فيها شيء مهم
وفي مرحلة ما، سندرك أن مستخدمي المكتبة يتوقعون أكثر من مجرد إشعارات البريد الإلكتروني. يرغب الكثير منهم في تلقي رسالة نصية قصيرة حول المشكلات الحرجة. ويرغب الآخرون في أن يتم إعلامهم على Facebook، وبالطبع، يرغب مستخدمو الشركات في الحصول على إشعارات Slack.
ما مدى صعوبة يمكن أن يكون هذا؟ لقد قمنا بتوسيع فئة Notifier ووضع طرق الإعلام الإضافية في فئات فرعية جديدة. والآن كان من المفترض أن يقوم العميل بإنشاء مثيل لفئة الإشعارات المطلوبة واستخدامها لجميع الإشعارات الإضافية.
ولكن بعد ذلك سألنا أحد الأشخاص بشكل معقول:”لماذا لا يمكننا استخدام عدة أنواع من الإشعارات في وقت واحد؟ إذا اشتعلت النيران في المنزل، فربما تريد أن يتم إعلامنا بذلك من خلال كل قناة.”
لقد حاولنا معالجة هذه المشكلة عن طريق إنشاء فئات فرعية خاصة تجمع بين عدة طرق إعلام داخل فئة واحدة. ومع ذلك، سرعان ما أصبح واضحاً أنَّ هذا النهج من شأنه أن يؤدي إلى تضخم الكود بشكل كبير، ليس فقط كود المكتبة ولكن كود العميل أيضًا.
علينا أن نجد طريقة أخرى لتنظيم فئات الإشعارات حتى لا يحطم رقمها بعض الأرقام القياسية في موسوعة غينيس عن طريق الخطأ.
- حل المشكلة باستخدام نمط التصميم”Decorator design pattern”
إنَّ توسيع الClass هو أول ما يتبادر إلى ذهننا عندما نحتاج إلى تغيير سلوك كائنٍ ما. ومع ذلك، فإنَّ الميراث (Inheritance) لديه العديد من المحاذير الخطيرة التي يجب أن نكون على دراية بها.
حيث أنَّ الميراث ثابت (Inheritance is static) لا يمكننا تغيير سلوك كائن موجود في وقت التشغيل. ويمكننا فقط استبدال الكائن بأكمله بكائن آخر تم إنشاؤه من فئة فرعية مختلفة.
ويمكن أن تحتوي الفئات الفرعية على فئة رئيسية واحدة فقط.ففي معظم اللغات، لا يسمح الوراثة للفصل بوراثة سلوكيات فئات متعددة في نفس الوقت.
وإحدى الطرق للتغلب على هذه التحذيرات هي استخدام التجميع أو التركيب بدلاً من الوراثة. يعمل كلا البديلين بنفس الطريقة تقريبًا: يحتوي أحد الكائنات على إشارة إلى كائن آخر ويفوضه بعض العمل، بينما في حالة الميراث، يكون الكائن نفسه قادراً على القيام بهذا العمل، ويرث السلوك من فئته الفائقة.
باستخدام هذا الأسلوب الجديد، يمكننا بسهولة استبدال الكائن “المساعد” المرتبط بكائن آخر، مما يؤدي إلى تغيير سلوك الحاوية في وقت التشغيل. يمكن للكائن استخدام سلوك فئات مختلفة، مع وجود إشارات إلى كائنات متعددة وتفويض جميع أنواع العمل إليها. التجميع/التركيب هو المبدأ الأساسي وراء العديد من أنماط التصميم، بما في ذلك Decorator. في هذه الأثناء، دعونا نعود إلى مناقشة النمط.
المغلَّف “Wrapper” هو الاسم المستعار البديل لنمط الديكور الذي يعبر بوضوح عن الفكرة الرئيسية للنمط. المجمع هو كائن يمكن ربطه ببعض الكائنات المستهدفة. يحتوي المجمع على نفس مجموعة الأساليب التي يحتوي عليها الهدف ويفوض إليه جميع الطلبات التي يتلقاها. ومع ذلك، قد يغير المجمّع النتيجة عن طريق القيام بشيء ما قبل أو بعد تمرير الطلب إلى الهدف.
متى يصبح الغلاف البسيط هو الديكور الحقيقي؟
كما ذكرنا ينفذ المجمّع نفس واجهة الكائن الملتف. ولهذا السبب فإنَّ هذه الأشياء متطابقة من وجهة نظر العميل. لذلك نجعل الحقل المرجعي للمجمّع يقبل أي كائن يتبع تلك الواجهة.وذلك سيتيح لنا تغطية كائن بأغلفة متعددة، وإضافة السلوك المدمج لجميع الأغلفة إليه.
في مثال الإشعارات الخاص بنا، دعونا نترك سلوك إشعار البريد الإلكتروني البسيط داخل فئة Notifier الأساسية، ولكن نحول جميع طرق الإشعارات الأخرى إلى أدوات تزيين.
سيحتاج كود العميل إلى تغليف كائن مُنبه أساسي في مجموعة من أدوات الديكور التي تتوافق مع تفضيلات العميل. سيتم تنظيم الكائنات الناتجة كمكدس.
سيكون آخر مصمم ديكور في المكدس هو الكائن الذي يعمل معه العميل بالفعل. نظراً لأنَّ جميع أدوات الديكور تنفذ نفس الواجهة مثل المنبه الأساسي، فلن تهتم بقية شيفرة العميل بما إذا كانت تعمل مع كائن التنبيه “الخالص” أو الكائن المزخرف.
ويمكننا تطبيق نفس النهج على السلوكيات الأخرى مثل تنسيق الرسائل أو إنشاء قائمة المستلمين. يمكن للعميل تزيين الكائن بأية أدوات تزيين مخصصة، طالما أنها تتبع نفس الواجهة مثل الآخرين.
- تشبيه العالم الحقيقي
نرتدي الملابس عندما نشعر بالبرد، حيث نُلفُّ نفسنا بسترة. وإذا كنا لا نزال نشعر بالبرد ونحن نرتدي سترة، يمكننا ارتداء سترة فوقها. إذا كانت السماء تمطر، يمكننا ارتداء معطف واق من المطر. كل هذه الملابس تعمل على “توسيع” سلوكنا الأساسي ولكنها ليست جزءاً منا، ويمكننا بسهولة خلع أي قطعة من الملابس عندما لا نحتاج إليها.
- بناء نمط التصميم الإنشائي”Decorator design pattern”
- يعلن المكوِّن (component) عن الواجهة المشتركة لكل من wrappers and wrapped objects
- المكوِّن (Concrete Component) هو فئة من الكائنات التي يتم تغليفها. فهو يحدِّد السلوك الأساسي الذي يمكن لمصممي الديكور تغييره.
- تحتوي (Base Decorator class) على حقل للإشارة إلى كائن ملتف. يجب الإعلان عن نوع الحقل كواجهة مكونة بحيث يمكن أن يحتوي على مكونات ملموسة وعناصر ديكور. يقوم المزخرف الأساسي بتفويض جميع العمليات إلى الكائن الملتف.
- يُحدِّد (Concrete Decorators) السلوكيات الإضافية التي يمكن إضافتها إلى المكونات ديناميكياً. تتجاوز أدوات الديكور أساليب الديكور الأساسي وتنفذ سلوكها إما قبل أو بعد استدعاء الطريقة الأصلية.
- يمكن للعميل (The Client) تغليف المكونات في طبقات متعددة من أدوات الديكور، طالما أنَّه يعمل مع جميع الكائنات عبر واجهة المكون.
- تمثيل نمط التصميم”Decorator design pattern”بشكل كود
في هذا المثال، يتيح لنا نمط الديكور ضغط البيانات الحساسة وتشفيرها بشكل مستقل عن التعليمات البرمجية التي تستخدم هذه البيانات فعلياً.
يقوم التطبيق بتغليف كائن مصدر البيانات بزوج من أدوات الديكور. يقوم كلا الغلافين بتغيير طريقة كتابة البيانات وقراءتها من القرص:
- قبل كتابة البيانات على القرص مباشرة، يقوم المزخرفون بتشفيرها وضغطها. تكتب الفئة الأصلية البيانات المشفرة والمحمية إلى الملف دون معرفة التغيير.
- مباشرة بعد قراءة البيانات من القرص، تمر عبر نفس برامج الديكور، التي تقوم بفك ضغطها وفك تشفيرها.
- تنفذ أدوات الديكور وفئة مصدر البيانات نفس الواجهة، مما يجعلها قابلة للتبديل في كود العميل.
- قابلية نمط التصميم”Decorator design pattern”للتطبيق
- نستخدم نمط الديكور عندما نحتاج إلى أن نكون قادرين على تعيين سلوكيات إضافية للكائنات في وقت التشغيل دون كسر التعليمات البرمجية التي تستخدم هذه الكائنات.
- نستخدم النمط عندما يكون من الصعب أو غير ممكن توسيع سلوك الكائن باستخدام الوراثة.
- كيفية تنفيذ نمط التصميم”Decorator design pattern”
- نتأكد من إمكانية تمثيل مجال عملنا كمكون أساسي مع طبقات اختيارية متعددة فوقه.
- اكتشاف الطرق الشائعة لكل من المكون الأساسي والطبقات الاختيارية حيث نقوم بإنشاء واجهة مكون وأعلن عن تلك الأساليب هناك.
- إنشاء فئة مكون ملموس وتحديد السلوك الأساسي فيها.
- نقوم بإنشاء فئة ديكور أساسية. يجب أن يحتوي على حقل لتخزين مرجع إلى كائن ملتف. يجب أن يتم الإعلان عن الحقل بنوع واجهة المكون للسماح بالارتباط بالمكونات الخرسانية وكذلك الديكورات. يجب أن يقوم مصمم الديكور الأساسي بتفويض كل العمل إلى الكائن الملتف.
- نتأكد من أن كافة الفئات تنفذ واجهة المكون.
- نقوم بإنشاء ديكورات عن طريق تمديدها من ديكور القاعدة حيث يجب على مصمم الديكور الملموس تنفيذ سلوكه قبل أو بعد استدعاء الأسلوب الأصل (الذي يفوض دائمًا إلى الكائن الملتف).
- يجب أن يكون رمز العميل مسؤولاً عن إنشاء الديكورات وتأليفها بالطريقة التي يحتاجها العميل.
- إيجابيات وسلبيات نمط التصميم”Decorator design pattern”
الإيجابيات:
- يمكنك توسيع سلوك الكائن دون إنشاء فئة فرعية جديدة.
- يمكنك إضافة أو إزالة المسؤوليات من كائن في وقت التشغيل.
- يمكنك الجمع بين العديد من السلوكيات عن طريق تغليف كائن في أدوات تزيين متعددة.
السلبيات:
- من الصعب إزالة غلاف معين من حزمة الأغلفة.
- من الصعب تنفيذ مصمم الديكور بطريقة لا يعتمد سلوكه على الترتيب الموجود في مجموعة الديكورات.
- قد يبدو رمز التكوين الأولي للطبقات قبيحًا جدًا.
- علاقات نمط التصميم”Decorator design pattern”مع الأنماط الأخرى
- يوفِّر Adapter واجهة مختلفة تماماً للوصول إلى كائن موجود. من ناحية أخرى، مع نمط Decorator ، تظل الواجهة كما هي أو يتم توسيعها. بالإضافة إلى ذلك، يدعم Decorator التركيب التكراري، وهو أمر غير ممكن عند استخدام المحول.
- باستخدام Adapter ، يمكنك الوصول إلى كائن موجود عبر واجهة مختلفة. مع Proxy، تظل الواجهة كما هي. باستخدام Decorator يمكنك الوصول إلى الكائن عبر واجهة محسَّنة.
- Chain of responsibility& Decorator لها هياكل طبقية متشابهة جداً. يعتمد كلا النمطين على التركيب العودي لتمرير التنفيذ عبر سلسلة من الكائنات. ومع ذلك، هناك العديد من الاختلافات الحاسمة.
- الخاتمة
وفي الختام، يُعدُّ نمط التصميم Decorator أداة قوية في هندسة البرمجيات لتحسين سلوك الكائنات ديناميكياً في وقت التشغيل. حيث إنَّه يُعزِّز المرونة وإعادة استخدام التعليمات البرمجية وفصل الاهتمامات من خلال السماح لنا بإضافة وظائف جديدة إلى الكائنات دون تغيير بنيتها.
ويوفر نمط الديكور حلاً مرناً وقابلاً للصيانة لإضافة أو تعديل الوظائف في الأنظمة الموجهة للكائنات، مما يجعله أداة قيمة في ترسانة مطور البرامج.