Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نمط المزين

جزء من قسم 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) عندما تحتاج إلى إضافة مسؤوليات إلى الكائنات دون التسبب في تضخم عدد الفئات الفرعية. كل مزيّن مستقل بذاته، لذا يمكنك المزج والمطابقة بينها بحرية لإنشاء مزيج السلوكيات الذي تحتاجه تماماً.

challenge icon

التحدي

سهل

لنقم ببناء نظام إشعارات باستخدام نمط المزين (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 المزين بالكامل مع الرسالة
        
    }
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming