Decoration Design Pattern

١٧ نوفمبر ٢٠٢٤

في كتاب Head First Design Patterns، وكان في مثال عن برمجة آلة مشروبات. خلينا نفترض عندنا مشروبات زي الشاي والإسبريسو. وبدنا نعمل برمجة الها بحيث لما يجي الزبون يطلب يطلعله وصف عن المشروب زي مثلا اسم المشروب ويعطيه السعر اول حل بيخطر ببال المبرمج عمل abstract class ويسميه Beverage، وفيه دالتين: cost و getDescription. بعدين عمل كلاس لكل مشروب، زي Espresso و Tea، وكل واحد فيهم ورث الفنكشنز اللي في الكلاس الأب ويعمل override لكل من السعر والوصف حسب شو بيناسبه.



abstract class Beverage {
    String description = "Unknown Beverage";
    public String getDescription() {
        return description;
    }
    public abstract double cost();
}
class Espresso extends Beverage {
    public Espresso() {
        description = "Espresso";
    }
    public double cost() {
        return 5.0;
    }
}

طيب لو بدنا نعمل اضافات للمشاريب زي مثلا السكر والحليب و...، فمثلاً، الإسبريسو ممكن نضيف عليه حليب أو سكر أو موكا، والشاي ممكن نضيف له بس سكر ، كيف نهدل الاضافات!! هل الحل إننا نعمل كلاس لكل حالة؟ يعني مثلاً TeaWithSugar أو EspressoWithMilk أو EspressoWithMocha؟ طيب لو صار عندنا إضافات كتيرة، والكلاسات كتيرة؟ ولو بدنا نعمل شاي بإضافة سكر مرتين كيف بدنا نهدل هاد الاشي؟ وتكلفة الإضافات كيف بدنا نحسبها؟ هل هاد منطقي!! فتعالو نشوف شو ممكن حلول مقترحة بتيجي ببالك أول حل: شو رايكم نضيف دوال في الكلاس الأب زي hasTea، hasSugar، hasMocha، و hasMilk، وكل دالة منهم بتاخد boolean وكل من tea و espress بيعملولهم override وبصير كل إضافة بنروح نعدل قيمة boolean ل true وممكن نعمل نغير السعر هاد الحل بيزبط بس المشكلة بتكمن في انو كل كلاس ابن لازم يعمل override لهاي الدوال. وطبعاً الشاي ما بنفع أضيف عليه موكا، فإيش أسوي؟ أرجع false دايما؟ مش منطقي إني أعمل override لأربع دوال وكلها ترجع false إلا وحدة ممكن ترجع true لو ضافها ولو بدي اخليه يغير السعر بدي اعمل طريقة تانية عشان اهندل السعر، طب لو عندي كمية مشروبات وكمية اضافات هيك حيصير أكيد حيصير الكود سلطة... طب شو الحل الافضل!! عشان نعرف الحل لازم نطلع من دائرة العلاقات is -a (inheritance) والانتقال ل has-a (composition) ال composition اني احقن obj كأنو خاصية في كلاس تاني كيف يعني!! نفرض عنا مصنع سيارات فالسيارة عشان تشتغل بدها محرك, عشان هيك السيارة لازم نعمل object من المحرك يا بنمررله اياه من برا يا بنعمله جوا كلاس ال car فهان رحنا حقننا المحرك في السيارة وممرنالو اياه كانو خاصية

class Engine {
    public function start() {
        return "Engine starts";
    }
}
class Car {
    private $engine;
    public function __construct(Engine $engine) {
        $this->engine = $engine;
    }
    public function start() {
        return $this->engine->start() . " and car is ready to go!";
    }
}
$car = new Car(new Engine());

فنرجع للمشكلة الاساسية كيف نعمل آلة المشاريبالحل اني استخدام Decorator Design Pattern. شو هاد decoration قلك تخيل إنك طلبت إسبريسو، وبعدين بتضيف عليه الإضافات كأنك بتغلفه بكل اضافة وبتهندل السعر ووالوصف بطريقة اسلس. فبدل ما تعمل كلاس لكل حالة، بتعمل كلاسات للمشروبات الأساسية زي Tea وEspresso، وبعدين تعمل abstract class جديد اسمه CondimentDecorators، وهاد بيمثل كل الإضافات زي السكر والموكا والحليب .وكل اضافة بتعمل وراثة من هاد الكلاس

class Mocha extends CondimentDecorator{
    Beverage beverage;
    public Mocha(Beverage beverage) {
        this.beverage = beverage;
    }
    public String getDescription() {
        return beverage.getDescription() + ", Mocha";
    }
    public double cost() {
        return beverage.cost() + 1.5;
    }
}

الحركة الحلوة هان إنك بتحقن كائن Beverage داخل كل إضافة هاد هوا مفهوم ال composition, يعني بتستخدم composition مش inheritance. ليش؟ عشان لما تجي تحسب التكلفة، بتجيب سعر المشروب الأساسي وتضيف عليه سعر الإضافة. ونفس الشي بالنسبة للوصف، بتدمج اسم الإضافة مع اسم المشروب. ليش خليت CondimentDecorators يرث من Beverage؟ عشان اطبق مفهوم بيليفورمزيم عشان كل الفنكشنات زي cost تكون موحدة، وكله يرجع من نوع Beverage. ولما تعمل مشروب زي Espresso مثلاً وبدنا نضيف عليه موكا مرتين ، بتنشئه كالتالي:

Beverage beverage = new Espresso();
beverage = new Moca(beverage);
beverage = new Moca(beverage);

ممكن تشوف الصور اللي مرفقها توصلك صورة شو بقصد بالحقن وكيف السعر بيطلع من الفقاعة الصغيرة اللي بتمثل المشروب الرئيسي وبعدين بتصير بيطلع للي بعدها وبيعدل السعر و...

وبالتاي سعر المشروب في المثال اللي فوق في البداية كان 5 تغير صار 6.5 وبعدها صار 8 والوصف تاعه تغير بشكل داينميك في الruntime مش انحط ك static بهذا الشكل، بتحل مشكلة التكلفة بكل سهولة، ولو بدك تضيف حجم (size) للمشروب وكل حجم بسعر مختلف، بتحدد الحجم أول ما تنشئ الكائن Beverage، وبداخل الإضافات بتفحص الحجم وتحسب السعر المناسب هيك اي اشي ممكن نهندله بدون ما نعقد الدنيا ويخرب كل تصميم الكود... هذا هو Decorator Design Pattern. فكرته إنك تستخدم composition يعني تحقن كائن داخل constructor بدل ما تكتب كلاس لكل حالة وتورث منها. الطريقة هاي أبسط وأوضح وأقل تعقيد. فكركم شو افضل نستخدم ال inheritance ولا composition!! طيب هل في طريقة احسن نهدل التبعيات هادي والحقن بطريقة تكون dynamic اكتر وتسهل علينا!!

"change behavior in runtime 😉"