نمط Decorator
نمط Decorator
يتيح لك نمط Decorator إضافة سلوكيات جديدة لكائن في وقت التشغيل بتغليفه داخل كائن آخر يشترك معه في الواجهة ذاتها. بدلًا من إنشاء تفجير تركيبي من الأصناف الفرعية، تُركّب مزخرفات صغيرة ومركّزة بأي ترتيب تحتاجه.
لماذا لا نلجأ إلى الوراثة فحسب؟
لنفترض أن لديك محرر نصوص TextEditor وتريد دعم التدقيق الإملائي والحفظ التلقائي وتمييز الكود — بأي مجموعة ممكنة. إنشاء فصيل فرعي لكل مجموعة يُعطيك سبعة أصناف لثلاث ميزات. أضف ميزة رابعة ويتضاعف العدد مجددًا. يتجاوز Decorator هذا كليًا: كل ميزة تعيش في صنف واحد ويُغلّف أي TextEditor آخر.
البنية
- Component — الواجهة (أو الصنف المجرّد) المشتركة بين الكائن الحقيقي وكل مزخرف.
- ConcreteComponent — التنفيذ الأساسي الذي تريد توسعته.
- BaseDecorator — يحمل مرجعًا لـ
Componentويفوّض جميع الاستدعاءات؛ الأصناف الفرعية تُجاوز الأساليب التي تحتاج إلى تعزيزها فقط. - ConcreteDecorators — تُضيف السلوك الفعلي قبل التفويض و/أو بعده.
مثال تحويل النصوص
ابدأ بواجهة مكوّن بسيطة وتنفيذ عادي:
يخزّن المزخرف الأساسي المكوّن المُغلَّف ويُعيد توجيه كل استدعاء إليه:
تُضيف المزخرفات الملموسة السلوك حول ذلك التفويض:
ركّبها بأي ترتيب عند موقع الاستدعاء:
java.io — مثال الحياة الواقعية الكلاسيكي لـ Decorator
تقوم تراتبية تدفقات java.io بأكملها على Decorator. فـ InputStream هو المكوّن؛ وFileInputStream وByteArrayInputStream وغيرهما هي المكوّنات الملموسة؛ وَFilterInputStream هو المزخرف الأساسي. كل صنف تُطبّقه فوق ذلك — BufferedInputStream، DataInputStream، GZIPInputStream — هو مزخرف ملموس.
يُضيف كل غلاف قدرة واحدة بالضبط — التخزين المؤقت، فك ترميز المحارف، ضغط gzip، قراءة الملف — دون أن يحتاج أي من هذه الأصناف إلى معرفة الأصناف الأخرى. يمكنك استبدال FileInputStream بـ ByteArrayInputStream (للاختبارات) دون المساس بأي طبقة أخرى.
المقايضات ومتى تستخدم النمط
- فضّله على الوراثة عندما تكون الميزات متعامدة حقًا وقابلة للتركيب.
- فضّله على الصنف الضخم الواحد الذي يُبدّل السلوك بالأعلام — تجعل المزخرفات كل اهتمام قابلًا للاختبار بمعزل.
- احذر من الحساسية للترتيب: حذف المسافات ثم التحويل للأحرف الكبيرة ليس كالتحويل للأحرف الكبيرة ثم حذف المسافات عند تعلّق الأمر بحالات الحروف المرتبطة بالمحلّية.
- تنكسر المساواة والهوية:
decorated.equals(original)تكونfalseدائمًا تقريبًا. لا تعتمد على هوية الكائن حين تكون المزخرفات في اللعبة. - الأكوام العميقة يصعب تصحيحها: سلسلة من عشرة مزخرفات قد تُعتم مصدر نتيجة غير متوقعة. أبقِ كل مزخرف صغيرًا ووثّق الترتيب المتوقع.
Decorator مع الواجهات الوظيفية في Java الحديثة (بديل حديث)
للتحويلات البسيطة ذات الأسلوب الواحد، يمنحك Function::andThen وFunction::compose نفس التركيب دون أي تراتبية صفية:
يظل نمط Decorator المبني على الأصناف هو الأداة الصحيحة حين تحتوي الواجهة على أساليب متعددة، أو حين تحمل المزخرفات حالة (كمزخرف العدّ)، أو حين تحتاج إلى تمرير المزخرف عبر كود يتوقع نوع المكوّن.
الخلاصة
يُغلّف Decorator كائنًا في كائن آخر يشاركه الواجهة، مُضيفًا السلوك دون المساس بالأصل. تدفقات java.io هي أشهر تطبيق له. أبقِ المزخرفات عديمة الحالة قدر الإمكان، وثّق ترتيب التركيب، وفضّل الأسلوب الوظيفي لخطوط التحويل الأحادية العملية. يتألق النمط كلما كان لديك مجموعة من السلوكيات المستقلة ذات المنفعة التي يجب أن تتحد بحرية في وقت التشغيل.