الفرق بين المراجعتين لصفحة: «Design Patterns/decorator»

من موسوعة حسوب
2.5 تمام المحتوى
3.0 إضافة صور
سطر 4: سطر 4:
== المشكلة ==
== المشكلة ==
'''ضع الصورة. يستطيع برنامج أن يستخدم فئة المنبه لإرسال إشعارات عن الأحداث المهمة إلى قائمة عناوين بريدية.'''
'''ضع الصورة. يستطيع برنامج أن يستخدم فئة المنبه لإرسال إشعارات عن الأحداث المهمة إلى قائمة عناوين بريدية.'''
 
[[ملف:dpd.problem1-en.png]]
تخيل أنك تعمل على مكتبة إشعارات تسمح للبرامج الأخرى بتنبيه مستخدميها إلى الأحداث المهمة، وبنيت النسخة الأولية من المكتبة على فئة <code>Notifier</code> بها بضعة حقول قليلة، ومنشئ (Constructor) وأسلوب <code>Send</code> وحيد، ويستطيع الأسلوب قبول وسيط في هيئة رسالة من عميل ويرسلها إلى قائمة من عناوين البريد التي يمررها المنشئ إلى المنبه (Notifier).
تخيل أنك تعمل على مكتبة إشعارات تسمح للبرامج الأخرى بتنبيه مستخدميها إلى الأحداث المهمة، وبنيت النسخة الأولية من المكتبة على فئة <code>Notifier</code> بها بضعة حقول قليلة، ومنشئ (Constructor) وأسلوب <code>Send</code> وحيد، ويستطيع الأسلوب قبول وسيط في هيئة رسالة من عميل ويرسلها إلى قائمة من عناوين البريد التي يمررها المنشئ إلى المنبه (Notifier).


سطر 10: سطر 10:


'''ضع الصورة. يُستخدم كل نوع تنبيه كفئة منبه فرعية.'''
'''ضع الصورة. يُستخدم كل نوع تنبيه كفئة منبه فرعية.'''
 
[[ملف:dpd.problem2.png]]
لهذا توسِّع فئة <code>Notifier</code> وتضع أساليب تنبيه إضافية في فئات فرعية، ويفترض بالعميل أن يعرف فئة التنبيه المطلوبة ويستخدمها لكل الإشعارات التالية، وستدرك أنك ستحتاج في الحالات الحرجة إلى إشعار المستخدم في كل القنوات التي هو فيها -فيس بوك، بريد، سلاك، ...- والطريقة المباشرة لفعل ذلك هي إنشاء فئات فرعية خاصة تجمع عدة أساليب إشعارات في فئة واحدة، لكن عيبها أن الشيفرة ستصبح ضخمة، سواء شيفرة المكتبة أو العميل، ونريد أن نجد طريقة أفضل لهيكلة فئات الإشعارات بحيث لا تكبر أعدادها بشكل زائد عن الحد.
لهذا توسِّع فئة <code>Notifier</code> وتضع أساليب تنبيه إضافية في فئات فرعية، ويفترض بالعميل أن يعرف فئة التنبيه المطلوبة ويستخدمها لكل الإشعارات التالية، وستدرك أنك ستحتاج في الحالات الحرجة إلى إشعار المستخدم في كل القنوات التي هو فيها -فيس بوك، بريد، سلاك، ...- والطريقة المباشرة لفعل ذلك هي إنشاء فئات فرعية خاصة تجمع عدة أساليب إشعارات في فئة واحدة، لكن عيبها أن الشيفرة ستصبح ضخمة، سواء شيفرة المكتبة أو العميل، ونريد أن نجد طريقة أفضل لهيكلة فئات الإشعارات بحيث لا تكبر أعدادها بشكل زائد عن الحد.


'''ضع الصورة. انفجار الفئات الفرعية.'''
'''ضع الصورة. انفجار الفئات الفرعية.'''
 
[[ملف:dpd.problem3.png]]
== الحل ==
== الحل ==
رغم أن توسعة الفئة هي أول ما يرد للذهن عند تغيير سلوك كائن ما إلا أن الاكتساب (Inheritance) له مؤشرات خطر يجب أن تدركها:
رغم أن توسعة الفئة هي أول ما يرد للذهن عند تغيير سلوك كائن ما إلا أن الاكتساب (Inheritance) له مؤشرات خطر يجب أن تدركها:
سطر 22: سطر 22:


'''ضع الصورة. الاكتساب مقابل التركيب.'''
'''ضع الصورة. الاكتساب مقابل التركيب.'''
 
[[ملف:dpd.solution1-en.png]]
ولنمط المزخرف اسم آخر يصف وظيفة النمط بوضوح، وهو "المغلِّف"، إذ أن المغلِّف هو كائن يمكن ربطه بكائن "هدف"، ويحتوي المغلِّف على نفس الأساليب التي في الكائن الهدف، ويفوِّض إليه كل الطلبات التي يستلمها، لكن قد يغير المغلِّف النتيجة بشيء ما قبل أو بعد تمريره الطلب إلى الهدف.
ولنمط المزخرف اسم آخر يصف وظيفة النمط بوضوح، وهو "المغلِّف"، إذ أن المغلِّف هو كائن يمكن ربطه بكائن "هدف"، ويحتوي المغلِّف على نفس الأساليب التي في الكائن الهدف، ويفوِّض إليه كل الطلبات التي يستلمها، لكن قد يغير المغلِّف النتيجة بشيء ما قبل أو بعد تمريره الطلب إلى الهدف.


سطر 28: سطر 28:


'''ضع الصورة. عدة أساليب إشعار تصبح مزخرِفات.'''
'''ضع الصورة. عدة أساليب إشعار تصبح مزخرِفات.'''
 
[[ملف:dpd.solution2.png]]
ستحتاج شيفرة العميل أن تغلف كائن إشعار بسيط بمجموعة مزخرفات تطابق تفضيلات العميل، وستهيكَل الكائنات في هيئة مكدَّس (Stack).
ستحتاج شيفرة العميل أن تغلف كائن إشعار بسيط بمجموعة مزخرفات تطابق تفضيلات العميل، وستهيكَل الكائنات في هيئة مكدَّس (Stack).


'''ضع الصورة. قد تهيئ التطبيقات تكديسات معقدة من مزخرفات الإشعارات.'''
'''ضع الصورة. قد تهيئ التطبيقات تكديسات معقدة من مزخرفات الإشعارات.'''
 
[[ملف:dpd.solution3.png]]
وسيكون آخر مزخرِف في المكدس هو الكائن الذي يعمل معه العميل، وبما أن كل المزخرِفات تستخدم نفس الواجهة التي يستخدمها المنبه الأساسي (Base Notifier)، فإن شيفرة العميل لن يهمها إن كانت تعمل مع كائن منبه "نقي" أو مزخرَف، ونستطيع تطبيق نفس المنظور على سلوكيات أخرى مثل تنسيق الرسائل أو تنظيم قائمة المستلمين، ويستطيع العميل زخرفة الكائن بأي مزخرِفات خاصة طالما أنها تتبع نفس الواجهة كالباقين.
وسيكون آخر مزخرِف في المكدس هو الكائن الذي يعمل معه العميل، وبما أن كل المزخرِفات تستخدم نفس الواجهة التي يستخدمها المنبه الأساسي (Base Notifier)، فإن شيفرة العميل لن يهمها إن كانت تعمل مع كائن منبه "نقي" أو مزخرَف، ونستطيع تطبيق نفس المنظور على سلوكيات أخرى مثل تنسيق الرسائل أو تنظيم قائمة المستلمين، ويستطيع العميل زخرفة الكائن بأي مزخرِفات خاصة طالما أنها تتبع نفس الواجهة كالباقين.


== مثال واقعي ==
== مثال واقعي ==
'''ضع الصورة. تحصل على أثر مجمع من ارتداء الملابس.'''


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


== البُنية ==
== البُنية ==
'''ضع الصورة.'''
'''ضع الصورة.''' [[ملف:dpd.structure-indexed.png]]
# يصرح '''الجزء (Component)''' عن واجهة مشتركة لكل من المغلِّفات والكائنات المغلَّفة.
# يصرح '''الجزء (Component)''' عن واجهة مشتركة لكل من المغلِّفات والكائنات المغلَّفة.
# '''الجزء الحقيقي (Concrete Component)''' هو فئة الكائنات يتم تغليفها، وتعرِّف السلوك الأساسي الذي يمكن تغييره بواسطة المزخرِفات.
# '''الجزء الحقيقي (Concrete Component)''' هو فئة الكائنات يتم تغليفها، وتعرِّف السلوك الأساسي الذي يمكن تغييره بواسطة المزخرِفات.
سطر 52: سطر 51:


'''ضع الصورة. مثال مزخرِف التشفير والضغط.'''
'''ضع الصورة. مثال مزخرِف التشفير والضغط.'''
 
[[ملف:dpd.example.png]]
يغلِّف التطبيق كائن مصدرِ البيانات بزوج من المزخرِفات، وكلا المزخرفين يغيران الطريقة التي تُكتب البيانات بها وتقرأ من القرص:
يغلِّف التطبيق كائن مصدرِ البيانات بزوج من المزخرِفات، وكلا المزخرفين يغيران الطريقة التي تُكتب البيانات بها وتقرأ من القرص:
* تشفر المزخرِفات البيانات وتضغطها قبل '''كتابتها على القرص''' مباشرة، وتكتب الفئةُ الأساسيةُ البيانات المحمية والمشفرة إلى الملف دون العلم بالتغيير.
* تشفر المزخرِفات البيانات وتضغطها قبل '''كتابتها على القرص''' مباشرة، وتكتب الفئةُ الأساسيةُ البيانات المحمية والمشفرة إلى الملف دون العلم بالتغيير.

مراجعة 00:39، 11 مارس 2019

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

المشكلة

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

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

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

ضع الصورة. انفجار الفئات الفرعية.

الحل

رغم أن توسعة الفئة هي أول ما يرد للذهن عند تغيير سلوك كائن ما إلا أن الاكتساب (Inheritance) له مؤشرات خطر يجب أن تدركها:

  • الاكتساب ساكن، فلا يمكنك تغيير سلوك كائن موجود فعلًا في وقت التشغيل (run time) ولا يمكنك فعل شيء سوى استبدال الكائن بكامله بواحد أنشاته من فئة فرعية.
  • لا يمكن أن يكون للفئات الفرعية سوى فئة أم وحيدة، ذلك أن الاكتساب لا يسمح في أغلب اللغات للفئة أن تكتسب سلوكيات من عدة فئات في نفس الوقت.

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

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

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

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

ضع الصورة. قد تهيئ التطبيقات تكديسات معقدة من مزخرفات الإشعارات. وسيكون آخر مزخرِف في المكدس هو الكائن الذي يعمل معه العميل، وبما أن كل المزخرِفات تستخدم نفس الواجهة التي يستخدمها المنبه الأساسي (Base Notifier)، فإن شيفرة العميل لن يهمها إن كانت تعمل مع كائن منبه "نقي" أو مزخرَف، ونستطيع تطبيق نفس المنظور على سلوكيات أخرى مثل تنسيق الرسائل أو تنظيم قائمة المستلمين، ويستطيع العميل زخرفة الكائن بأي مزخرِفات خاصة طالما أنها تتبع نفس الواجهة كالباقين.

مثال واقعي

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

البُنية

ضع الصورة.

  1. يصرح الجزء (Component) عن واجهة مشتركة لكل من المغلِّفات والكائنات المغلَّفة.
  2. الجزء الحقيقي (Concrete Component) هو فئة الكائنات يتم تغليفها، وتعرِّف السلوك الأساسي الذي يمكن تغييره بواسطة المزخرِفات.
  3. فئة المزخرف الأساسي (Base Decorator) لها حقل للإشارة إلى كائن مغلَّف، ويجب أن يصرَّح عن نوع الحقل على أنه واجهة الجزء كي يستطيع احتواء كلًا من الأجزاء الحقيقية والمزخرِفات. ويفوِّض المزخرِف الأساسي العمليات كلها إلى الكائن المغلَّف.
  4. تعرِّف المزخرِفات الحقيقية (Concrete Decorators) سلوكيات إضافية يمكن أن تضاف إلى الأجزاء بديناميكية، وتتخطى المزخرِفات الحقيقية أساليب المزخرِف الأساسي وتنفذ أسلوبها قبل أو بعد استدعاء الأسلوب الأم (Parent Behavior).
  5. العميل (Client) يستطيع تغليف الأجزاء في طبقات متعددة من المزخرِفات طالما أنها تعمل مع كل الكائنات من خلال واجهة الجزء (Component Interface).

مثال توضيحي

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

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

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

تستخدم المزخرِفات وفئةُ مصدرِ البيانات نفس الواجهة التي تجعلها كلها قابلة للتعديل بشكل متبادل (interchangeable) في شيفرة العميل.

// تعرِّف واجهة الجزء العملياتَ التي يمكن تغييرها بواسطة المزخرفات.
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 مرر البيانات المشفرة إلى أسلوب 

    method readData():data is
        // 1. للكائن المغلَّف readData اجلب البيانات من أسلوب.
        // 2. حاول فك تشفيرها إن كانت مشفرة.
        // 3. أعد النتيجة.

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

    method readData():data is
        // 1. للكائن المغلَّف readData اجلب البيانات من أسلوب.
        // 2. حاول فك ضغطها إن كانت مضغوطة.
        // 3. أعد النتيجة.


// خيار1. مثال بسيط لتجميع المزخرف.
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)
        // يحتوي متغير المصدر الآن على هذا:
        // Encryption > Compression > FileDataSource
        source.writeData(salaryRecords)
        // كُتب الملف الهدف ببيانات مشفرة ومضغوطة.



// خيار 2. شيفرة العميل التي تستخدم مصدر بيانات خارجي.
// أو تهتم بتفاصيل تخزين البيانات، إذ تعمل مع مصدر بيانات معد مسبقًا SalaryManager لا تعرف كائنات  
// (app configurator) تم استقباله من قبل مهيئ التطبيق.
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()
    // ...

قابلية الاستخدام

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

يسمح لك نمط المزخرف بهيكلة منطق تجارتك (Business Logic) في هيئة طبقات، فأنشئ مزخرِفًا لكل طبقة ونظم الكائنات مع تجميعات مختلفة من هذا المنطق في وقت التشغيل. تستطيع شيفرة العميل أن تعامل كل تلك الكائنات بنفس الطريقة بما أنها جميعًا تتبع نفس الواجهة المشتركة.

استخدم نمط المزخرِف عند عدم إمكانية توسعة سلوك الكائن باستخدام الاكتساب (Inheritance).

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

كيفية الاستخدام

  1. تأكد أن نطاقك التجاري يمكن تمثيله كجزء أساسي مع عدة طبقات اختيارية فوقه.
  2. اكتشف أي الأساليب التي تشترك فيها الطبقات الخيارية مع الجزء الأساسي، ومن ثم أنشئ واجهة للجزء وصرح عن تلك الأساليب هناك.
  3. أنشئ فئة جزء حقيقي وعرَّف السلوك الأساسي فيها.
  4. أنشئ فئة مزخرِف أساسي، وينبغي أن يكون فيها حقل لتخزين مرجع إلى الكائن المُغلَّف، ويجب أن يُصرَّح عن الحقل مع نوع واجهة الجزء (Component Interface Type) ليسمح بالربط مع الأجزاء الحقيقية والمزخرِفات، ويجب أن يفوض المزخرِف الأساسي كل المهام إلى الكائن المغلَّف.
  5. تأكد أن كل الفئات تستخدم واجهة الجزء.
  6. أنشئ مزخرَفات حقيقية بتوسيعها من المزخرِف الأساسي، ويجب أن ينفذ المزخرف الحقيقي سلوكه قبل أو بعد الاستدعاء إلى الأسلوب الأم (Parent Method)، والذي يفوض بدوره المهام إلى الكائن المغلَّف.
  7. يجب أن تكون شيفرة العميل مسؤولة عن إنشاء المزخرِفات وترتيبها بالطريقة التي يحتاجها العميل.

المزايا والعيوب

المزايا

  1. توسيع سلوك الكائن دون إنشاء فئة فرعية جديدة.
  2. إضافة أو حذف مسؤوليات من الكائن أثناء التشغيل.
  3. جمع عدة سلوكيات من خلال تغليف كائن بعدة مزخرِفات.
  4. مبدأ المسؤولية الواحدة. يمكنك تقسيم فئة أحادية تستخدم عدة صور محتملة من السلوكيات إلى عدة فئات أصغر منها.

العيوب

  1. صعوبة حذف مغلِّف ما من المكدِّسات (Stacks).
  2. صعوبة استخدام مزخرِف بحيث لا يعتمد سلوكه على الترتيب داخل مكدِّس المزخرِفات.
  3. قد تبدو شيفرة الإعدادات الأولية قبيحة في البداية.

العلاقات مع الأنماط الأخرى

  • يغير نمط المحوِّل واجهة كائن موجود فعلًا بينما يحسِّن المزخرِفُ الكائنَ دون تغيير واجهته، وإضافة لهذا فإن المزخرف يدعم التركيب التكراري (Recursive Composition) الذي يستحيل عند استخدام المحول.
  • يقدم المحول واجهة مختلفة للكائن المغلَّف، ويقدم نمط الوكيل (Proxy) نفس الواجهة له، أما المزخرِف فيقدم واجهة محسَّنة.
  • يتشابه نمطيْ المزخرِف وسلسلة المسؤوليات في هيكلة الفئات إذ يعتمد كلا النمطين على التركيب التكراري لتمرير التنفيذ خلال سلسلة كائنات، لكن بأي حال هناك فروق كبيرة أخرى بينهما، فسلسلة المسؤوليات تستطيع تنفيذ عمليات عشوائية بشكل مستقل عن بعضها، كما تستطيع إيقاف تمرير الطلب عند أي نقطة لما بعدها، ومن الناحية الأخرى تستطيع مزخرفات عديدة توسيع سلوك الكائن مع الحفاظ عليه متناسقًا مع الواجهة الأساسية، وإضافة لهذا فلا يُسمح للمزخرِفات أن تعطل سير الطلب.
  • للمركَّب والمزخرِف مخططات هيكلية متشابهة بما أن كليهما يعتمدان على التركيب التكراري لتنظيم عدد مفتوح النهاية من الكائنات. ويشبه المزخرِفُ المركَّبَ لكنه لا يملك إلا عنصرًا فرعيًا واحدًا، كما أن المزخرف يضيف مسؤوليات إضافية إلى الكائن المغلَّف في حين أن المركب لا يزيد على جمع النتائج من فروعه. لكن هذين النمطين قد يتعاونان فيما بينهما إذ يمكنك استخدام المزخرف لتوسيع سلوك كائن بعينه في شجرة المركَّب.
  • التصميمات التي تستخدم المركب والمزخرف بكثرة تستفيد من استخدام النموذج الأولي، ذلك أن استخدامه يسمح لك بنسخ هياكل معقدة بدلًا من إعادة إنشائهم من الصفر.
  • يسمح المزخرف بتغيير مظهر كائن ما، بينما يسمح لك نمط الخطة بتغيير جوهره.
  • رغم تشابه المزخرف والوكيل في الهيكلة إلا أنهما يختلفان تمامًا في الهدف منهما، فالنمطين كلاهما قد بنيا على مبدأ التركيب حيث يفترض بكائن ما أن يفوض بعضًا من مهامه إلى غيره، لكن الفرق هو أن الوكيل عادة ما يدير دورة حياة كائن الخدمة الخاصة به بنفسه، بينما يتحكم العميل دومًا في تركيب المزخرفات.

الاستخدام في لغة جافا

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ☆

أمثلة الاستخدام: يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams). إليك بعض الأمثلة على المزخرِف في مكتبات جافا:

يمكن ملاحظة نمط المزخرف من خلال الأساليب الإنشائية أو المنشئات التي تقبل كائنات من نفس النوع/الواجهة الحالية.

مثال: مزخرفات التشفير والضغط

يوضح المثال كيفية تعديل سلوك كائن ما دون تغيير شيفرته، ففي البداية كان المنطق التجاري (Business Logic) يستطيع قراءة وكتابة البيانات في صورة نص بسيط (Plain text)، ثم أنشأنا عدة فئات مغلِّفات صغيرة تضيف سلوكًا جديدًا بعد تنفيذ العمليات القياسية في كائن مغلَّف. وأول مغلِّف يشفر البيانات ويفك تشفيرها، أما الثاني فيضغطها ويفك ضغطها، وتستطيع جمع أو دمج تلك المغلِّفات من خلال تغليف مزخرف داخل آخر.

المزخرفات

decorators/DataSource.java: واجهة بيانات مشتركة، تعرِّف عمليات القراءة والكتابة
package refactoring_guru.decorator.example.decorators;

public interface DataSource {
    void writeData(String data);

    String readData();
}
 decorators/FileDataSource.java: قارئ/كاتب بيانات بسيط
package refactoring_guru.decorator.example.decorators;

import java.io.*;

public class FileDataSource implements DataSource {
    private String name;

    public FileDataSource(String name) {
        this.name = name;
    }

    @Override
    public void writeData(String data) {
        File file = new File(name);
        try (OutputStream fos = new FileOutputStream(file)) {
            fos.write(data.getBytes(), 0, data.length());
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
    }

    @Override
    public String readData() {
        char[] buffer = null;
        File file = new File(name);
        try (FileReader reader = new FileReader(file)) {
            buffer = new char[(int) file.length()];
            reader.read(buffer);
        } catch (IOException ex) {
            System.out.println(ex.getMessage());
        }
        return new String(buffer);
    }
}
 decorators/DataSourceDecorator.java: مزخرف أساسي مجرد
package refactoring_guru.decorator.example.decorators;

public class DataSourceDecorator implements DataSource {
    private DataSource wrappee;

    DataSourceDecorator(DataSource source) {
        this.wrappee = source;
    }

    @Override
    public void writeData(String data) {
        wrappee.writeData(data);
    }

    @Override
    public String readData() {
        return wrappee.readData();
    }
}
 decorators/EncryptionDecorator.java: مزخرف التشفير
package refactoring_guru.decorator.example.decorators;

import java.util.Base64;

public class EncryptionDecorator extends DataSourceDecorator {

    public EncryptionDecorator(DataSource source) {
        super(source);
    }

    @Override
    public void writeData(String data) {
        super.writeData(encode(data));
    }

    @Override
    public String readData() {
        return decode(super.readData());
    }

    private String encode(String data) {
        byte[] result = data.getBytes();
        for (int i = 0; i < result.length; i++) {
            result[i] += (byte) 1;
        }
        return Base64.getEncoder().encodeToString(result);
    }

    private String decode(String data) {
        byte[] result = Base64.getDecoder().decode(data);
        for (int i = 0; i < result.length; i++) {
            result[i] -= (byte) 1;
        }
        return new String(result);
    }
}
 decorators/CompressionDecorator.java: مزخرف الضغط
package refactoring_guru.decorator.example.decorators;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;

public class CompressionDecorator extends DataSourceDecorator {
    private int compLevel = 6;

    public CompressionDecorator(DataSource source) {
        super(source);
    }

    public int getCompressionLevel() {
        return compLevel;
    }

    public void setCompressionLevel(int value) {
        compLevel = value;
    }

    @Override
    public void writeData(String data) {
        super.writeData(compress(data));
    }

    @Override
    public String readData() {
        return decompress(super.readData());
    }

    private String compress(String stringData) {
        byte[] data = stringData.getBytes();
        try {
            ByteArrayOutputStream bout = new ByteArrayOutputStream(512);
            DeflaterOutputStream dos = new DeflaterOutputStream(bout, new Deflater(compLevel));
            dos.write(data);
            dos.close();
            bout.close();
            return Base64.getEncoder().encodeToString(bout.toByteArray());
        } catch (IOException ex) {
            return null;
        }
    }

    private String decompress(String stringData) {
        byte[] data = Base64.getDecoder().decode(stringData);
        try {
            InputStream in = new ByteArrayInputStream(data);
            InflaterInputStream iin = new InflaterInputStream(in);
            ByteArrayOutputStream bout = new ByteArrayOutputStream(512);
            int b;
            while ((b = iin.read()) != -1) {
                bout.write(b);
            }
            in.close();
            iin.close();
            bout.close();
            return new String(bout.toByteArray());
        } catch (IOException ex) {
            return null;
        }
    }
}
 Demo.java: شيفرة العميل
package refactoring_guru.decorator.example;

import refactoring_guru.decorator.example.decorators.*;

public class Demo {
    public static void main(String[] args) {
        String salaryRecords = "Name,Salary\nJohn Smith,100000\nSteven Jobs,912000";
        DataSourceDecorator encoded = new CompressionDecorator(
                                         new EncryptionDecorator(
                                             new FileDataSource("out/OutputDemo.txt")));
        encoded.writeData(salaryRecords);
        DataSource plain = new FileDataSource("out/OutputDemo.txt");

        System.out.println("- Input ----------------");
        System.out.println(salaryRecords);
        System.out.println("- Encoded --------------");
        System.out.println(plain.readData());
        System.out.println("- Decoded --------------");
        System.out.println(encoded.readData());
    }
}
 OutputDemo.txt: نتائج التنفيذ
- Input ----------------
Name,Salary
John Smith,100000
Steven Jobs,912000
- Encoded --------------
Zkt7e1Q5eU8yUm1Qe0ZsdHJ2VXp6dDBKVnhrUHtUe0sxRUYxQkJIdjVLTVZ0dVI5Q2IwOXFISmVUMU5rcENCQmdxRlByaD4+
- Decoded --------------
Name,Salary
John Smith,100000
Steven Jobs,912000

الاستخدام في لغة #C

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ☆

أمثلة الاستخدام: يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams).

يمكن ملاحظة نمط المزخرف من خلال الأساليب الإنشائية أو المنشئات التي تقبل كائنات من نفس النوع/الواجهة الحالية.

مثال تصوري

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

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

Program.cs: مثال تصوري

using System;

namespace RefactoringGuru.DesignPatterns.Composite.Conceptual
{
    // العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة الجزء الأساسي.
    public abstract class Component
    {
        public abstract string Operation();
    }

    // تقدم الأجزاء الحقيقية استخدامات افتراضية للعمليات، وقد تكون هناك صور متعددة من تلك الفئات.
    class ConcreteComponent : Component
    {
        public override string Operation()
        {
            return "ConcreteComponent";
        }
    }

    // تتبع فئة المزخرف الأساسي نفس واجهة الأجزاء الأخرى، والغرض الأساسي من هذه الفئة هو تعريف واجهة
    // التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين جزء مغلَّف
    // وكذلك أساليب استدعائه.
    abstract class Decorator : Component
    {
        protected Component _component;

        public Decorator(Component component)
        {
            this._component = component;
        }

        public void SetComponent(Component component)
        {
            this._component = component;
        }

        // يفوض المزخرِف كل المهام إلى الجزء المُغلَّف.
        public override string Operation()
        {
            if (this._component != null)
            {
                return this._component.Operation();
            }
            else
            {
                return string.Empty;
            }
        }
    }

    // تستدعي المزخرفات الحقيقيةُ الكائنَ المغلَّف وتغير نتيجته بشكل ما.

    class ConcreteDecoratorA : Decorator
    {
        public ConcreteDecoratorA(Component comp) : base(comp)
        {
        }

        // للعملية بدلًا من استدعاء (parent implementation) قد تستدعي المزخرفات الاستخدام الأم 
        // الكائن المغلَّف مباشرة، وهذه الطريقة تبسط توسعة فئات المزخرف.
        public override string Operation()
        {
            return $"ConcreteDecoratorA({base.Operation()})";
        }
    }

    // تستطيع المزخرفات تنفيذ سلوكها قبل أو بعد استدعاء الكائن المُغلَّف.
    class ConcreteDecoratorB : Decorator
    {
        public ConcreteDecoratorB(Component comp) : base(comp)
        {
        }

        public override string Operation()
        {
            return $"ConcreteDecoratorB({base.Operation()})";
        }
    }
    
    public class Client
    {
        // تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة الجزء، وهكذا تظل مستقلة عن الفئات الحقيقية
        // للأجزاء التي تعمل معها.
        public void ClientCode(Component component)
        {
            Console.WriteLine("RESULT: " + component.Operation());
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            Client client = new Client();

            var simple = new ConcreteComponent();
            Console.WriteLine("Client: I get a simple component:");
            client.ClientCode(simple);
            Console.WriteLine();

            // ...إضافة إلى الأجزاء المزخرفة أيضًا.
            //
            // لاحظ كيف تستطيع المزخرِفات تغليف الأجزاء البسيطة والمزخرفات 
            // الأخرى كذلك.
            ConcreteDecoratorA decorator1 = new ConcreteDecoratorA(simple);
            ConcreteDecoratorB decorator2 = new ConcreteDecoratorB(decorator1);
            Console.WriteLine("Client: Now I've got a decorated component:");
            client.ClientCode(decorator2);
        }
    }
}

 Output.txt: نتائج التنفيذ

Client: I get a simple component:
RESULT: ConcreteComponent

Client: Now I've got a decorated component:
RESULT: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))

الاستخدام في لغة PHP

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ☆

أمثلة الاستخدام: يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams).

مثال تصوري

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

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

سيكون من السهل عليك استيعاب المثال التالي بعد تعلم بنية النمط، بناء على استخدام واقعي له في لغة PHP.

index.php: مثال تصوري

<?php

namespace RefactoringGuru\Decorator\Conceptual;

/**
 * العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة الجزء الأساسي.
 */
interface Component
{
    public function operation(): string;
}

/**
 * تقدم الأجزاء الحقيقية استخدامات افتراضية للعمليات، وقد تكون هناك صور متعددة من تلك الفئات.
 */
class ConcreteComponent implements Component
{
    public function operation(): string
    {
        return "ConcreteComponent";
    }
}

/**
 * تتبع فئة المزخرف الأساسي نفس واجهة الأجزاء الأخرى، والغرض الأساسي من هذه الفئة هو تعريف واجهة
 * التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين جزء مغلَّف
 * وكذلك أساليب استدعائه.
 */
class Decorator implements Component
{
    /**
     * @var Component
     */
    protected $component;

    public function __construct(Component $component)
    {
        $this->component = $component;
    }

    /**
     * يفوض المزخرِف كل المهام إلى الجزء المُغلَّف.
     */
    public function operation(): string
    {
        return $this->component->operation();
    }
}

/**
 * تستدعي المزخرفات الحقيقيةُ الكائنَ المغلَّف وتغير نتيجته بشكل ما.
 */
class ConcreteDecoratorA extends Decorator
{
    /**
     * للعملية بدلًا من استدعاء (parent implementation) قد تستدعي المزخرفات الاستخدام الأم 
     * الكائن المغلَّف مباشرة، وهذه الطريقة تبسط توسعة فئات المزخرف.
     */
    public function operation(): string
    {
        return "ConcreteDecoratorA(" . parent::operation() . ")";
    }
}

/**
 * تستطيع المزخرفات تنفيذ سلوكها قبل أو بعد استدعاء الكائن المُغلَّف.
 */
class ConcreteDecoratorB extends Decorator
{
    public function operation(): string
    {
        return "ConcreteDecoratorB(" . parent::operation() . ")";
    }
}

/**
 * تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة الجزء، وهكذا تظل مستقلة عن الفئات الحقيقية
 * للأجزاء التي تعمل معها.
 */
function clientCode(Component $component)
{
    // ...

    echo "RESULT: " . $component->operation();

    // ...
}

/**
 * وهكذا تستطيع شيفرة العميل أن تدعم الأجزاء البسيطة...
 */
$simple = new ConcreteComponent;
echo "Client: I've got a simple component:\n";
clientCode($simple);
echo "\n\n";

/**
 * ...إضافة إلى الأجزاء المزخرَفة.
 *
 * لاحظ كيف تستطيع المزخرِفات تغليف الأجزاء البسيطة والمزخرِفات الأخرى كذلك.
 */
$decorator1 = new ConcreteDecoratorA($simple);
$decorator2 = new ConcreteDecoratorB($decorator1);
echo "Client: Now I've got a decorated component:\n";
clientCode($decorator2);

 Output.txt: نتائج التنفيذ

Client: I've got a simple component:
RESULT: ConcreteComponent

Client: Now I've got a decorated component:
RESULT: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))

مثال واقعي

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

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

 index.php: مثال واقعي

<?php

namespace RefactoringGuru\Decorator\RealWorld;

/**
 * تصرِّح واجهة الجزء عن أسلوب ترشيح يجب استخدامه من قِبل جميع الأجزاء الحقيقية
 * والمزخرِفات.
 */
interface InputFormat
{
    public function formatText(string $text): string;
}

/**
 * الجزء الحقيقي عنصر أساسي في الزخرفة إذ يحتوي على النص الأصلي كما هو دون
 * ترشيح أو تنسيق.
 */
class TextInput implements InputFormat
{
    public function formatText(string $text): string
    {
        return $text;
    }
}

/**
 * لا تحتوي فئة المزخرف الأساسية على أي ترشيح حقيقي أو منطق تنسيق، ووظيفتها الأساسية هي 
 * تنفيذ بنية الزخرفة التحتية الأساسية:
 * حقل لتخزين جزء مغلَّف أو مزخرِف آخر، وأسلوب التنسيق الأساسي الذي يفوض العمل إلى الكائن
 * المغلَّف.
 * تنفَّذ مهمة التنسيق الحقيقية بواسطة الفئات الفرعية.
 */
class TextFormat implements InputFormat
{
    /**
     * @var InputFormat
     */
    protected $inputFormat;

    public function __construct(InputFormat $inputFormat)
    {
        $this->inputFormat = $inputFormat;
    }

    /**
     * يفوض المزخرِف المهام الأساسية إلى الجزء المغلَّف.
     */
    public function formatText(string $text): string
    {
        return $this->inputFormat->formatText($text);
    }
}

/**
 * HTML هذا المزخرِف الحقيقي يجرد النص من كل وسوم.
 */
class PlainTextFilter extends TextFormat
{
    public function formatText(string $text): string
    {
        $text = parent::formatText($text);
        return strip_tags($text);
    }
}

/**
 * التي قد (Attributes) الخطرة فقط، والوسائط HTML هذا المزخرف الحقيقي يجرد وسوم \
 * XSS تسبب ثغرات.
 */
class DangerousHTMLTagsFilter extends TextFormat
{
    private $dangerousTagPatterns = [
        "|<script.*?>([\s\S]*)?</script>|i", // ...
    ];

    private $dangerousAttributes = [
        "onclick", "onkeypress", // ...
    ];


    public function formatText(string $text): string
    {
        $text = parent::formatText($text);

        foreach ($this->dangerousTagPatterns as $pattern) {
            $text = preg_replace($pattern, '', $text);
        }

        foreach ($this->dangerousAttributes as $attribute) {
            $text = preg_replace_callback('|<(.*?)>|', function ($matches) use ($attribute) {
                $result = preg_replace("|$attribute=|i", '', $matches[1]);
                return "<" . $result . ">";
            }, $text);
        }

        return $text;
    }
}

/**
 * HTML إلى Markdown هذا المزخرف الحقيقي يضيف تحويلًا أوليًا من.
 */
class MarkdownFormat extends TextFormat
{
    public function formatText(string $text): string
    {
        $text = parent::formatText($text);

        // (Block Elements) نسِّق عناصر المتن.
        $chunks = preg_split('|\n\n|', $text);
        foreach ($chunks as &$chunk) {
            // Format headers.
            if (preg_match('|^#+|', $chunk)) {
                $chunk = preg_replace_callback('|^(#+)(.*?)$|', function ($matches) {
                    $h = strlen($matches[1]);
                    return "<h$h>" . trim($matches[2]) . "</h$h>";
                }, $chunk);
            } // نسِّق الفقرات.
            else {
                $chunk = "<p>$chunk</p>";
            }
        }
        $text = implode("\n\n", $chunks);

        // (inline) نسِّق العناصر السطرية.
        $text = preg_replace("|__(.*?)__|", '<strong>$1</strong>', $text);
        $text = preg_replace("|\*\*(.*?)\*\*|", '<strong>$1</strong>', $text);
        $text = preg_replace("|_(.*?)_|", '<em>$1</em>', $text);
        $text = preg_replace("|\*(.*?)\*|", '<em>$1</em>', $text);

        return $text;
    }
}


/**
 * قد تكون شيفرة العميل جزءًا من موقع حقيقي يخرج محتوىً ينتجه المستخدمون، فلا يعنيها 
 * إن كانت تحصل على كائن جزء بسيط أو مزخرَف يما أنها تعمل مع المنسِّقات من خلال واجهة
 * الجزء.
 */
function displayCommentAsAWebsite(InputFormat $format, string $text)
{
    // ..

    echo $format->formatText($text);

    // ..
}

/**
 * منسقات الإدخال مفيدة عند التعامل مع المحتوى الذي ينتجه المستخدمون، فقد يكون عرض ذلك
 * المحتوى كما هو خطيرًا للغاية، خاصة في حالة إنتاجه من قبل مستخدمين مجهولين كما في 
 * XSS حالة التعليقات، ذلك أن موقعك حينها يكون معرضًا لهجمات السخام وهجمات
 */
$dangerousComment = <<<HERE
Hello! Nice blog post!
Please visit my <a href='http://www.iwillhackyou.com'>homepage</a>.
<script src="http://www.iwillhackyou.com/script.js">
  performXSSAttack();
</script>
HERE;

/**
 * (معالجة بسيطة للتعليقات (غير آمنة.
 */
$naiveInput = new TextInput;
echo "Website renders comments without filtering (unsafe):\n";
displayCommentAsAWebsite($naiveInput, $dangerousComment);
echo "\n\n\n";

/**
 * (معالجة مرشَّحة للتعليقات (آمنة.
 */
$filteredInput = new PlainTextFilter($naiveInput);
echo "Website renders comments after stripping all tags (safe):\n";
displayCommentAsAWebsite($filteredInput, $dangerousComment);
echo "\n\n\n";


/**
 * يسمح المزخرف بتكديس عدة صيغ إدخال للحصول على تحكم دقيق على المحتوى المعالَج.
 */
$dangerousForumPost = <<<HERE
# Welcome

This is my first post on this **gorgeous** forum.

<script src="http://www.iwillhackyou.com/script.js">
  performXSSAttack();
</script>
HERE;

/**
 * (معالجة بسيطة للمنشور (غير آمنة، وبدون تنسيق.
 */
$naiveInput = new TextInput;
echo "Website renders a forum post without filtering and formatting (unsafe, ugly):\n";
displayCommentAsAWebsite($naiveInput, $dangerousForumPost);
echo "\n\n\n";

/**
 * (ترشيح الوسوم الخطرة (آمن، حسن المظهر + Markdown منسِّق .
 */
$text = new TextInput;
$markdown = new MarkdownFormat($text);
$filteredInput = new DangerousHTMLTagsFilter($markdown);
echo "Website renders a forum post after translating markdown markup" .
    "and filtering some dangerous HTML tags and attributes (safe, pretty):\n";
displayCommentAsAWebsite($filteredInput, $dangerousForumPost);
echo "\n\n\n";

 Output.txt: نتائج التنفيذ

Website renders comments without filtering (unsafe):
Hello! Nice blog post!
Please visit my <a href='http://www.iwillhackyou.com'>homepage</a>.
<script src="http://www.iwillhackyou.com/script.js">
  performXSSAttack();
</script>


Website renders comments after stripping all tags (safe):
Hello! Nice blog post!
Please visit my homepage.

  performXSSAttack();



Website renders a forum post without filtering and formatting (unsafe, ugly):
# Welcome

This is my first post on this **gorgeous** forum.

<script src="http://www.iwillhackyou.com/script.js">
  performXSSAttack();
</script>


Website renders a forum post after translating markdown markupand filtering some dangerous HTML tags and attributes (safe, pretty):
<h1>Welcome</h1>

<p>This is my first post on this <strong>gorgeous</strong> forum.</p>

<p></p>

الاستخدام في لغة بايثون

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ☆

أمثلة الاستخدام: يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams).

يمكن ملاحظة نمط المزخرف من خلال الأساليب الإنشائية أو المنشئات التي تقبل كائنات من نفس النوع/الواجهة الحالية.

مثال تصوري

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

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

 main.py: مثال تصوري

class Component():
    """
    العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة الجزء الأساسي.
    """

    def operation(self) -> str:
        pass


class ConcreteComponent(Component):
    """
    تقدم المكونات الحقيقية استخدامات افتراضية للعمليات، وقد تكون هناك صور متعددة من تلك الفئات.
    """

    def operation(self) -> str:
        return "ConcreteComponent"


class Decorator(Component):
    """
     تتبع فئة المزخرف الأساسي نفس واجهة الأجزاء الأخرى، والغرض الأساسي من هذه الفئة هو تعريف واجهة
     التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين جزء مغلَّف
     وكذلك أساليب استدعائه.
    """

    _component: Component = None

    def __init__(self, component: Component) -> None:
        self._component = component

    @property
    def component(self) -> str:
        """
        يفوض المزخرِف كل المهام إلى الجزء المُغلَّف.
        """

        return self._component

    def operation(self) -> str:
        self._component.operation()


class ConcreteDecoratorA(Decorator):
    """
    تستدعي المزخرفات الحقيقيةُ الكائنَ المغلَّف وتغير نتيجته بشكل ما.
    """

    def operation(self) -> str:
        """
        للعملية بدلًا من استدعاء (parent implementation) قد تستدعي المزخرفات الاستخدام الأم 
        الكائن المغلَّف مباشرة، وهذه الطريقة تبسط توسعة فئات المزخرف.
        """
        return f"ConcreteDecoratorA({self.component.operation()})"


class ConcreteDecoratorB(Decorator):
    """
    تستطيع المزخرفات تنفيذ سلوكها قبل أو بعد استدعاء الكائن المُغلَّف.
    """

    def operation(self) -> str:
        return f"ConcreteDecoratorB({self.component.operation()})"


def client_code(component: Component) -> None:
    """
    تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة الجزء، وهكذا تظل مستقلة عن الفئات الحقيقية.
    """

    # ...

    print(f"RESULT: {component.operation()}", end="")

    # ...


if __name__ == "__main__":
    # وهكذا تستطيع شيفرة العميل أن تدعم الأجزاء البسيطة...
    simple = ConcreteComponent()
    print("Client: I've got a simple component:")
    client_code(simple)
    print("\n")

    # ...إضافة إلى الأجزاء المزخرفة أيضًا.
    #
    # لاحظ كيف تستطيع المزخرِفات تغليف الأجزاء البسيطة والمزخرفات  الأخرى كذلك.
    decorator1 = ConcreteDecoratorA(simple)
    decorator2 = ConcreteDecoratorB(decorator1)
    print("Client: Now I've got a decorated component:")
    client_code(decorator2)

 Output.txt: نتائج التنفيذ

Client: I've got a simple component:
RESULT: ConcreteComponent

Client: Now I've got a decorated component:
RESULT: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))

الاستخدام في لغة Swift

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ☆

أمثلة الاستخدام: يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams).

يمكن ملاحظة نمط المزخرف من خلال الأساليب الإنشائية أو المنشئات التي تقبل كائنات من نفس النوع/الواجهة الحالية.

مثال تصوري

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

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

 Example.swift: مثال تصوري

import XCTest

/// العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة الجزء الأساسي.
protocol Component {

    func operation() -> String
}

/// تقدم الأجزاء الحقيقية استخدامات افتراضية للعمليات، وقد تكون هناك صور متعددة من تلك الفئات.
class ConcreteComponent: Component {

    func operation() -> String {
        return "ConcreteComponent"
    }
}

/// تتبع فئة المزخرف الأساسي نفس واجهة الأجزاء الأخرى، والغرض الأساسي من هذه الفئة هو تعريف واجهة
/// التغليف لكل المزخرفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين جزء مغلَّف
/// وكذلك أساليب استدعائه.
class Decorator: Component {

    private var component: Component

    init(_ component: Component) {
        self.component = component
    }

    /// يفوض المزخرِف كل المهام إلى الجزء المُغلَّف.
    func operation() -> String {
        return component.operation()
    }
}

/// تستدعي المزخرفات الحقيقيةُ الكائنَ المغلَّف وتغير نتيجته بشكل ما.
class ConcreteDecoratorA: Decorator {

    /// للعملية بدلًا من استدعاء (parent implementation) قد تستدعي المزخرفات الاستخدام الأم
    /// الكائن المغلَّف مباشرة، وهذه الطريقة تبسط توسعة فئات المزخرف.
    override func operation() -> String {
        return "ConcreteDecoratorA(" + super.operation() + ")"
    }
}

/// تستطيع المزخرفات تنفيذ سلوكها قبل أو بعد استدعاء الكائن المُغلَّف.
class ConcreteDecoratorB: Decorator {

    override func operation() -> String {
        return "ConcreteDecoratorB(" + super.operation() + ")"
    }
}

/// تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة الجزء، وهكذا تظل مستقلة عن الفئات الحقيقية
/// للأجزاء التي تعمل معها.
class Client {
    // ...
    static func someClientCode(component: Component) {
        print("Result: " + component.operation())
    }
    // ...
}

/// دعنا نرى كيف ستعمل كل تلك الأجزاء معًا.
class DecoratorConceptual: XCTestCase {

    func testDecoratorStructure() {
        // وهكذا تستطيع شيفرة العميل أن تدعم الأجزاء البسيطة...
        print("Client: I've got a simple component")
        let simple = ConcreteComponent()
        Client.someClientCode(component: simple)

        // ...إضافة إلى الأجزاء المزخرفة أيضًا
        //
        // لاحظ كيف تستطيع المزخرِفات تغليف الأجزاء البسيطة والمزخرفات الأخرى كذلك.
        let decorator1 = ConcreteDecoratorA(simple)
        let decorator2 = ConcreteDecoratorB(decorator1)
        print("\nClient: Now I've got a decorated component")
        Client.someClientCode(component: decorator2)
    }
}

 Output.txt: نتائج التنفيذ

Client: I've got a simple component
Result: ConcreteComponent

Client: Now I've got a decorated component
Result: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))

مثال واقعي

 Example.swift: شيفرة العميل

import UIKit
import XCTest


protocol ImageEditor: CustomStringConvertible {

    func apply() -> UIImage
}

class ImageDecorator: ImageEditor {

    private var editor: ImageEditor

    required init(_ editor: ImageEditor) {
        self.editor = editor
    }

    func apply() -> UIImage {
        print(editor.description + " applies changes")
        return editor.apply()
    }

    var description: String {
        return "ImageDecorator"
    }
}

extension UIImage: ImageEditor {

    func apply() -> UIImage {
        return self
    }

    open override var description: String {
        return "Image"
    }
}



class BaseFilter: ImageDecorator {

    fileprivate var filter: CIFilter?

    init(editor: ImageEditor, filterName: String) {
        self.filter = CIFilter(name: filterName)
        super.init(editor)
    }

    required init(_ editor: ImageEditor) {
        super.init(editor)
    }

    override func apply() -> UIImage {

        let image = super.apply()
        let context = CIContext(options: nil)

        filter?.setValue(CIImage(image: image), forKey: kCIInputImageKey)

        guard let output = filter?.outputImage else { return image }
        guard let coreImage = context.createCGImage(output, from: output.extent) else {
            return image
        }
        return UIImage(cgImage: coreImage)
    }

    override var description: String {
        return "BaseFilter"
    }
}

class BlurFilter: BaseFilter {

    required init(_ editor: ImageEditor) {
        super.init(editor: editor, filterName: "CIGaussianBlur")
    }

    func update(radius: Double) {
        filter?.setValue(radius, forKey: "inputRadius")
    }

    override var description: String {
        return "BlurFilter"
    }
}

class ColorFilter: BaseFilter {

    required init(_ editor: ImageEditor) {
        super.init(editor: editor, filterName: "CIColorControls")
    }

    func update(saturation: Double) {
        filter?.setValue(saturation, forKey: "inputSaturation")
    }

    func update(brightness: Double) {
        filter?.setValue(brightness, forKey: "inputBrightness")
    }

    func update(contrast: Double) {
        filter?.setValue(contrast, forKey: "inputContrast")
    }

    override var description: String {
        return "ColorFilter"
    }
}

class Resizer: ImageDecorator {

    private var xScale: CGFloat = 0
    private var yScale: CGFloat = 0
    private var hasAlpha = false

    convenience init(_ editor: ImageEditor, xScale: CGFloat = 0, yScale: CGFloat = 0, hasAlpha: Bool = false) {
        self.init(editor)
        self.xScale = xScale
        self.yScale = yScale
        self.hasAlpha = hasAlpha
    }

    required init(_ editor: ImageEditor) {
        super.init(editor)
    }

    override func apply() -> UIImage {

        let image = super.apply()

        let size = image.size.applying(CGAffineTransform(scaleX: xScale, y: yScale))

        UIGraphicsBeginImageContextWithOptions(size, !hasAlpha, UIScreen.main.scale)
        image.draw(in: CGRect(origin: .zero, size: size))

        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return scaledImage ?? image
    }

    override var description: String {
        return "Resizer"
    }
}


class DecoratorRealWorld: XCTestCase {

    func testDecoratorReal() {

        let image = loadImage()

        print("Client: set up an editors stack")
        let resizer = Resizer(image, xScale: 0.2, yScale: 0.2)

        let blurFilter = BlurFilter(resizer)
        blurFilter.update(radius: 2)

        let colorFilter = ColorFilter(blurFilter)
        colorFilter.update(contrast: 0.53)
        colorFilter.update(brightness: 0.12)
        colorFilter.update(saturation: 4)

        clientCode(editor: colorFilter)
    }

    func clientCode(editor: ImageEditor) {
        let image = editor.apply()
        /// Note. You can stop an execution in Xcode to see an image preview.
        print("Client: all changes have been applied for \(image)")
    }
}

private extension DecoratorRealWorld {

    func loadImage() -> UIImage {

        let urlString = "https:// refactoring.guru/images/content-public/logos/logo-new-3x.png"

        /// Note:
        /// Do not download images the following way in a production code.

        guard let url = URL(string: urlString) else {
            fatalError("Please enter a valid URL")
        }

        guard let data = try? Data(contentsOf: url) else {
            fatalError("Cannot load an image")
        }

        guard let image = UIImage(data: data) else {
            fatalError("Cannot create an image from data")
        }
        return image
    }
}

 Output.txt: نتائج التنفيذ

Client: set up an editors stack

BlurFilter applies changes
Resizer applies changes
Image applies changes

Client: all changes have been applied for Image

الاستخدام في لغة TypeScript

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ☆

أمثلة الاستخدام: يكثر استخدام نمط المزخرِف في شيفرة جافا، خاصة في الشيفرات المتعلقة بالمتدفقات (streams).

يمكن ملاحظة نمط المزخرف من خلال الأساليب الإنشائية أو المنشئات التي تقبل كائنات من نفس النوع/الواجهة الحالية.

مثال تصوري

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

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

 index.ts: مثال تصوري

/**
 * العملياتَ التي يمكن تغييرها بواسطة المزخرفات (Base Component) تعرِّف واجهة الجزء الأساسي.
 */
interface Component {
    operation(): string;
}

/**
 * تقدم الأجزاء الحقيقية استخدامات افتراضية للعمليات، وقد تكون هناك صور متعددة من تلك الفئات.
 */
class ConcreteComponent implements Component {
    public operation(): string {
        return 'ConcreteComponent';
    }
}

/**
 * تتبع فئة المزخرف الأساسي نفس واجهة الأجزاء الأخرى، والغرض الأساسي من هذه الفئة هو تعريف واجهة
 * التغليف لكل المزخرِفات الحقيقية، وقد يشمل الاستخدام الافتراضي لشيفرة التغليف حقلًا لتخزين
 * جزء مغلَّف وكذلك أساليب استدعائه.
 */
class Decorator implements Component {
    protected component: Component;

    constructor(component: Component) {
        this.component = component;
    }

    /**
     * يفوض المزخرِف كل المهام إلى الجزء المُغلَّف.
     */
    public operation(): string {
        return this.component.operation();
    }
}

/**
 * تستدعي المزخرفات الحقيقيةُ الكائنَ المغلَّف وتغير نتيجته بشكل ما.
 */
class ConcreteDecoratorA extends Decorator {
    /**
     * للعملية بدلًا من استدعاء (parent implementation) قد تستدعي المزخرفات الاستخدام الأم 
     * الكائن المغلَّف مباشرة، وهذه الطريقة تبسط توسعة فئات المزخرِف.
     */
    public operation(): string {
        return `ConcreteDecoratorA(${super.operation()})`;
    }
}

/**
 * تستطيع المزخرفات تنفيذ سلوكها قبل أو بعد استدعاء الكائن المُغلَّف.
 */
class ConcreteDecoratorB extends Decorator {
    public operation(): string {
        return `ConcreteDecoratorB(${super.operation()})`;
    }
}

/**
 * تعمل شيفرة العميل مع كل الكائنات باستخدام واجهة الجزء، وهكذا تظل مستقلة عن الفئات الحقيقية
 * للأجزاء التي تعمل معها.
 */
function clientCode(component: Component) {
    // ...

    console.log(`RESULT: ${component.operation()}`);

    // ...
}

/**
 * وهكذا تستطيع شيفرة العميل أن تدعم الأجزاء البسيطة...
 */
const simple = new ConcreteComponent();
console.log('Client: I\'ve got a simple component:');
clientCode(simple);
console.log('');

/**
 * ...إضافة إلى الأجزاء المزخرَفة.
 *
 * لاحظ كيف تستطيع المزخرِفات تغليف الأجزاء البسيطة والمزخرِفات الأخرى كذلك.
 */
const decorator1 = new ConcreteDecoratorA(simple);
const decorator2 = new ConcreteDecoratorB(decorator1);
console.log('Client: Now I\'ve got a decorated component:');
clientCode(decorator2);

 Output.txt: نتائج التنفيذ

Client: I've got a simple component:
RESULT: ConcreteComponent

Client: Now I've got a decorated component:
RESULT: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))

انظر أيضًا

مصادر

توثيق نمط المزخرِف في موقع refactoring.guru.