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

نمط المزين

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

challenge icon

التحدي

سهل

لنَبْنِ نظام إشعارات باستخدام نمط Decorator! ستنشئ إشعارًا أساسيًا يمكن تحسينه باستخدام قنوات تسليم مختلفة، وتنبيهات البريد الإلكتروني، وتنبيهات SMS، وإشعارات الدفع، وذلك من خلال تغليف decorators حول بعضها بعضًا. وهذا يحاكي الأنظمة الواقعية التي قد تحتاج فيها رسالة واحدة إلى الإرسال عبر قنوات متعددة في الوقت نفسه.

ستنظّم الشيفرة في أربعة ملفات:

  • Notifier.java: عرّف واجهة Notifier التي تعمل كواجهة component لنمط decorator الخاص بك. يجب أن تعلن عن دالة send(String message) التي تتولى إرسال الإشعارات.
  • BasicNotifier.java: أنشئ فئة component concrete التي تنفّذ Notifier. ويمثل ذلك أبسط شكل من أشكال الإشعارات: تنبيه داخل التطبيق. عند استدعاء send، يجب أن تطبع In-App: [message].
  • NotifierDecorators.java: أنشئ تسلسل decorator الهرمي هنا. ابدأ بفئة NotifierDecorator abstract التي تنفّذ 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 المزخرف بالكامل مع الرسالة
        
    }
}
quiz iconاختبر نفسك

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

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Java عبر الإنترنت