نمط المزين
جزء من قسم Object Oriented Programming في رحلة Java على Coddy — الدرس 72 من 87.
إن نمط المزين (Decorator Pattern) هو نمط تصميم هيكلي يتيح لك إرفاق سلوكيات جديدة للكائنات عن طريق تغليفها في كائنات خاصة تسمى "المزينين" (decorators). وعلى عكس الوراثة، التي تضيف السلوك في وقت التجميع (compile time)، يضيف المزينون الوظائف بشكل ديناميكي في وقت التشغيل (runtime) دون تعديل الفئة الأصلية.
تخيل مقهى تبدأ فيه بقهوة أساسية ويمكنك إضافة إضافات مثل الحليب أو السكر أو الكريمة المخفوقة. كل إضافة تغلف القهوة الأصلية، وتضيف تكلفتها ووصفها. يستخدم هذا النمط واجهة مشتركة بحيث يمكن التعامل مع الكائنات المزينة بنفس طريقة التعامل مع الكائن الأصلي:
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) بمرجع للكائن المغلف ويقوم بتفويض الاستدعاءات إليه مع إضافة سلوكه الخاص. يمكنك تكديس عدة مزخرفات لدمج الميزات:
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 Pattern) عندما تحتاج إلى إضافة مسؤوليات إلى الكائنات دون التسبب في تضخم عدد الفئات الفرعية. كل مزيّن مستقل بذاته، لذا يمكنك المزج والمطابقة بينها بحرية لإنشاء مزيج السلوكيات الذي تحتاجه تماماً.
التحدي
سهللنقم ببناء نظام إشعارات باستخدام نمط المزين (Decorator Pattern)! ستقوم بإنشاء إشعار أساسي يمكن تحسينه بقنوات توصيل مختلفة—تنبيهات البريد الإلكتروني، تنبيهات SMS، وإشعارات الدفع (push notifications)—عن طريق تغليف المزينين حول بعضهم البعض. هذا يحاكي الأنظمة الواقعية حيث قد تحتاج رسالة واحدة إلى الخروج عبر قنوات متعددة في وقت واحد.
ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:
Notifier.java: قم بتعريف واجهةNotifierالتي تعمل كواجهة للمكون (component interface) لنمط المزين الخاص بك. يجب أن تصرح عن طريقةsend(String message)التي تتعامل مع إرسال الإشعارات.BasicNotifier.java: أنشئ فئة المكون الملموسة (concrete component class) التي تنفذNotifier. يمثل هذا أبسط شكل من أشكال الإشعارات—تنبيه داخل التطبيق. عند استدعاءsend، يجب أن تطبع"In-App: " + message.NotifierDecorators.java: قم ببناء تسلسل المزينين الهرمي هنا. ابدأ بفئة مجردةNotifierDecoratorتنفذNotifierوتحتفظ بمرجع لكائنNotifierمغلف (يتم تمريره عبر منشئها).ثم أنشئ ثلاثة مزينين ملموسين يوسعون
NotifierDecorator:EmailDecorator- عند استدعاءsend، يقوم أولاً بتفويض المهمة للمزين المغلف، ثم يطبع"Email: " + message.SMSDecorator- عند استدعاءsend، يقوم أولاً بتفويض المهمة للمزين المغلف، ثم يطبع"SMS: " + message.PushDecorator- عند استدعاءsend، يقوم أولاً بتفويض المهمة للمزين المغلف، ثم يطبع"Push: " + message.Main.java: اجمع نظام المزين الخاص بك معاً! ستتلقى مدخلين: رسالة الإشعار (String) وقائمة مفصولة بفاصلة للقنوات المراد إضافتها (String)، على سبيل المثال"email,sms"أو"push,email,sms".ابدأ بـ
BasicNotifier. ثم، بناءً على مدخلات القنوات، قم بتغليفه بالمزينين المناسبين بالترتيب الذي يظهرون به. أسماء القنوات الصالحة هي"email"و"sms"و"push".أخيراً، استدعِ
sendمع رسالتك على المزين المكتمل تغليفه.
ستتلقى مدخلين بالترتيب: الرسالة المراد إرسالها (String) والقنوات المراد تفعيلها (String، مفصولة بفاصلة).
على سبيل المثال، مع المدخلات "Server is down!" و "email,push"، سيكون مخرجك هو:
In-App: Server is down!
Email: Server is down!
Push: Server is down!لاحظ كيف يضيف كل مزين سلوكه الخاص مع الحفاظ على الإشعار الأصلي. يحدد ترتيب التغليف ترتيب المخرجات—المكون الداخلي (BasicNotifier) ينفذ أولاً، ثم يضيف كل مزين طبقته. يمكنك مزج ومطابقة أي مجموعة من القنوات دون إنشاء فئات منفصلة لكل مجموعة!
ورقة مرجعية
يُعد نمط المزيّن (Decorator Pattern) نمط تصميم هيكلي يضيف سلوكيات جديدة للكائنات عن طريق تغليفها في كائنات مزينة. فهو يضيف وظائف بشكل ديناميكي في وقت التشغيل دون تعديل الفئة الأصلية.
يستخدم هذا النمط واجهة مشتركة بحيث يمكن التعامل مع الكائنات المزينة بنفس الطريقة التي يتم بها التعامل مع الكائن الأصلي.
الهيكل الأساسي
تعريف واجهة المكون (Component Interface):
interface Coffee {
String getDescription();
double getCost();
}إنشاء مكون ملموس (Concrete Component) ينفذ الواجهة:
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}إنشاء فئة مزينة مجردة (Abstract Decorator) تنفذ نفس الواجهة وتحتفظ بمرجع للكائن المغلف:
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}إنشاء مزينات ملموسة (Concrete Decorators) ترث من المزين المجرد:
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;
}
}استخدام المزينات
تجميع عدة مزينات لدمج الميزات:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // Simple Coffee, Milk, Sugar
System.out.println(order.getCost()); // 2.75يقوم المزين بتفويض الاستدعاءات إلى الكائن المغلف مع إضافة سلوكه الخاص. كل مزين مستقل ويمكن دمجه ومطابقته بحرية.
جرّب بنفسك
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 المزين بالكامل مع الرسالة
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
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أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المكرر