نمط المزين
جزء من قسم البرمجة كائنية التوجه في رحلة Java على Coddy. الدرس 72 من 87.
إن Decorator Pattern هو نمط تصميم هيكلي يتيح لك إرفاق سلوكيات جديدة بالكائنات من خلال تغليفها داخل كائنات خاصة تُسمى decorators. وعلى خلاف الوراثة، التي تضيف السلوك في وقت الترجمة، تضيف decorators الوظائف ديناميكيًا في وقت التشغيل دون تعديل class الأصلية.
تخيّل متجرًا للقهوة حيث تبدأ بـ coffee أساسي ويمكنك إضافة إضافات مثل الحليب أو السكر أو الكريمة المخفوقة. يغلّف كلٌّ من هذه الإضافات coffee الأصلي، مضيفًا تكلفته ووصفه. يستخدم هذا النمط واجهة مشتركة بحيث يمكن التعامل مع الكائنات decorated بالطريقة نفسها التي يُتعامل بها مع الكائن الأصلي:
interface Coffee {
String getDescription();
double getCost();
}
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}يحتفظ decorator بمرجع إلى الكائن المُغلَّف ويفوّض الاستدعاءات إليه مع إضافة سلوكه الخاص. يمكنك تكديس عدة decorators لدمج الميزات:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // قهوة بسيطة، حليب، سكر
System.out.println(order.getCost()); // 2.75يتألق نمط Decorator عندما تحتاج إلى إضافة مسؤوليات إلى الكائنات دون إنشاء عدد هائل من الفئات الفرعية. كل decorator مستقل، لذا يمكنك مزجها ومطابقتها بحرية لإنشاء مجموعة السلوكيات التي تحتاج إليها بالضبط.
التحدي
سهللنَبْنِ نظام إشعارات باستخدام نمط Decorator! ستنشئ إشعارًا أساسيًا يمكن تحسينه باستخدام قنوات تسليم مختلفة، وتنبيهات البريد الإلكتروني، وتنبيهات SMS، وإشعارات الدفع، وذلك من خلال تغليف decorators حول بعضها بعضًا. وهذا يحاكي الأنظمة الواقعية التي قد تحتاج فيها رسالة واحدة إلى الإرسال عبر قنوات متعددة في الوقت نفسه.
ستنظّم الشيفرة في أربعة ملفات:
Notifier.java: عرّف واجهةNotifierالتي تعمل كواجهة component لنمط decorator الخاص بك. يجب أن تعلن عن دالةsend(String message)التي تتولى إرسال الإشعارات.BasicNotifier.java: أنشئ فئة component concrete التي تنفّذNotifier. ويمثل ذلك أبسط شكل من أشكال الإشعارات: تنبيه داخل التطبيق. عند استدعاءsend، يجب أن تطبعIn-App: [message].NotifierDecorators.java: أنشئ تسلسل decorator الهرمي هنا. ابدأ بفئةNotifierDecoratorabstract التي تنفّذNotifierوتحتفظ بمرجع إلى كائنNotifierمغلّف (يتم تمريره عبر constructor الخاص بها).ثم أنشئ ثلاثة decorators concrete تمتد من
NotifierDecorator:EmailDecorator- عند استدعاءsend، تفوّض العملية أولًا إلى notifier المغلّف، ثم تطبعEmail: [message].SMSDecorator- عند استدعاءsend، تفوّض العملية أولًا إلى notifier المغلّف، ثم تطبعSMS: [message].PushDecorator- عند استدعاءsend، تفوّض العملية أولًا إلى notifier المغلّف، ثم تطبعPush: [message].Main.java: اجمع نظام decorator الخاص بك! ستتلقى مُدخلين: رسالة الإشعار (String) وقائمة مفصولة بفواصل بالقنوات المطلوب إضافتها (String)، مثل"email,sms"أو"push,email,sms".ابدأ باستخدام
BasicNotifier. ثم، بناءً على مُدخل القنوات، قم بتغليفه باستخدام decorators المناسبة بالترتيب الذي تظهر به. أسماء القنوات الصالحة هيemailوsmsوpush.أخيرًا، استدعِ
sendمع رسالتك على notifier المغلّف بالكامل.
ستتلقى مُدخلين بالترتيب: الرسالة المطلوب إرسالها (String) والقنوات المطلوب تفعيلها (String، مفصولة بفواصل).
على سبيل المثال، باستخدام المُدخلين Server is down! وemail,push، سيكون الناتج:
In-App: Server is down!
Email: Server is down!
Push: Server is down!لاحظ كيف يضيف كل decorator سلوكه الخاص مع الحفاظ على الإشعار الأصلي. يحدد ترتيب التغليف ترتيب الناتج: ينفّذ component الداخلي (BasicNotifier) أولًا، ثم يضيف كل decorator طبقته. يمكنك مزج أي مجموعة من القنوات ومطابقتها دون إنشاء فئات منفصلة لكل مجموعة!
جرّب بنفسك
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// قراءة الرسالة المراد إرسالها
String message = scanner.nextLine();
// قراءة قائمة القنوات المفصولة بفواصل (على سبيل المثال: "email,sms" أو "push,email,sms")
String channelsInput = scanner.nextLine();
// TODO: البدء بـ BasicNotifier
// TODO: تقسيم مدخلات القنوات بواسطة الفاصلة
// TODO: المرور على كل قناة وتغليف الـ notifier بالـ decorator المناسب
// القنوات الصالحة هي: "email", "sms", "push"
// TODO: استدعاء send على الـ notifier المزخرف بالكامل مع الرسالة
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP
الملفات الخارجيةمقدمة في الـ OOPالفئات (Classes) مقابل الكائنات (Objects)الكلمة المفتاحية thisالدوال (Methods)الحقول (Attributes)دالة البناء (Constructor)تعدد أشكال دالة البناء (Constructor Overloading)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسية (extends)الكلمة المفتاحية superإعادة تعريف الدوال (@Override)تسلسل المنشئات (Constructor Chaining)كلاس Objectالوراثة الأحادية ومتعددة المستوياتلماذا لا توجد وراثة متعددة للكلاساتمراجعة - هيكلية الموظفين7الدوال الخاصة وفئة Object
دالة toString()equals() و hashCode()دالة clone()compareTo() و Comparableواجهة Comparatorمراجعة - الترتيب المخصص2محددات الوصول والتغليف
نظرة عامة على مستويات الوصولدوال Getter و Setterإخفاء البياناتالكلمة المفتاحية finalمراجعة - مدير الحساب البنكي5تعدد الأشكال (Polymorphism)
أساسيات الـ Method Overloadingالـ Method Overriding (في وقت التشغيل)الـ Upcasting والـ Downcastingمعامل الـ instanceofالفئات والدوال المجردة (Abstract)مراجعة - حاسبة الأشكال8مفاهيم OOP المتقدمة
الـ Composition مقابل الـ Inheritanceالـ Aggregation مقابل الـ Compositionالفئات الداخلية والمتداخلة والمجهولة (Inner, Nested & Anonymous)الـ Enums ودوالهاالـ Records (Java 16+)الـ Sealed Classes (Java 17+)11أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Builderنمط Observerنمط Strategy3خصائص الفئة والعضو الساكن
المتغيرات الخاصة بالكائن مقابل المتغيرات الساكنةالدوال الساكنةالكتل البرمجية الساكنةالثوابت (static final)ملخص - العداد والأدوات المساعدة6الواجهات (Interfaces) والأصناف المجردة (Abstract Classes)
مقدمة في الواجهات (Interfaces)تنفيذ الواجهات (Interfaces)تنفيذ واجهات متعددةالـ Default و Static في الواجهاتالأصناف المجردة مقابل الواجهاتالواجهات الوظيفية (Functional Interfaces)مراجعة - نظام الدفع9الأنواع العامة (Generics)
مقدمة في الأنواع العامةالأصناف العامة (Generic Classes)الدوال العامة (Generic Methods)معاملات الأنواع المقيدةالرموز البديلة (?, extends, super)مراجعة - الحاويات العامة12أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكررتدرّب بنفسك: مترجم Java عبر الإنترنت