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

نمط المزين

جزء من قسم Object Oriented Programming في رحلة PHP على Coddy — الدرس 77 من 91.

إن نمط المزين (Decorator Pattern) هو نمط تصميم هيكلي يتيح لك إضافة سلوكيات جديدة إلى الكائنات ديناميكيًا عن طريق تغليفها في كائنات تغليف خاصة تسمى المزينين (decorators). وعلى عكس الوراثة، التي تضيف السلوك في وقت التجميع (compile time)، فإن المزينين يضيفون السلوك في وقت التشغيل (runtime).

تخيل مقهى. تبدأ بقهوة أساسية، ثم تضيف إضافات مثل الحليب أو السكر أو الكريمة المخفوقة.

تغلف كل إضافة القهوة الأصلية وتضيف إلى وصفها وتكلفتها. الفكرة الجوهرية هي أن كلاً من القهوة الأساسية والمزخرفات (decorators) يتشاركان في نفس الواجهة (interface).

<?php
interface Coffee {
    public function getDescription(): string;
    public function getCost(): float;
}

class SimpleCoffee implements Coffee {
    public function getDescription(): string {
        return "Simple Coffee";
    }
    
    public function getCost(): float {
        return 2.00;
    }
}

تنفذ المزخرفات (Decorators) نفس الواجهة وتغلف كائن Coffee آخر:

<?php
class MilkDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Milk";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.50;
    }
}

class SugarDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Sugar";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.25;
    }
}

$coffee = new SimpleCoffee();
$coffee = new MilkDecorator($coffee);
$coffee = new SugarDecorator($coffee);

echo $coffee->getDescription() . " = $" . $coffee->getCost();

المخرجات:

Simple Coffee, Milk, Sugar = $2.75

يقوم كل مزخرف (decorator) بتغليف الكائن السابق، مما يشكل سلسلة. يتيح لك هذا النمط دمج السلوكيات بأي ترتيب وبأي كمية دون إنشاء فئة (class) لكل مجموعة محتملة. هل تحتاج إلى حليب مضاعف؟ فقط قم بتغليفه مرتين.

challenge icon

التحدي

سهل

لنقم ببناء نظام لطلب البيتزا باستخدام نمط المزين (Decorator Pattern). ستبدأ ببيتزا أساسية وتضيف إليها الإضافات ديناميكيًا — كل إضافة تغلف البيتزا وتضيف إلى وصفها وسعرها، تمامًا مثل مثال القهوة من الدرس.

ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:

  • Pizza.php — حدد واجهة Pizza التي تضع العقد لجميع أنواع البيتزا والإضافات. يجب أن تحتوي على طريقتين: getDescription(): string و getPrice(): float.
  • BasePizza.php — قم بتضمين واجهة Pizza وأنشئ فئة MargheritaPizza التي تنفذها. هذه هي البيتزا الأساسية الخاصة بك بوصف "Margherita" وسعر 8.00.
  • Toppings.php — قم بتضمين واجهة Pizza وأنشئ ثلاث فئات مزينة (decorator classes) تنفذها. كل مزين يغلف كائن Pizza (استقبله في المنشئ/constructor) ويعزز كلاً من الوصف والسعر:
    • CheeseTopping — يضيف ", Extra Cheese" إلى الوصف و 1.50 إلى السعر
    • PepperoniTopping — يضيف ", Pepperoni" إلى الوصف و 2.00 إلى السعر
    • MushroomTopping — يضيف ", Mushrooms" إلى الوصف و 1.25 إلى السعر
  • main.php — قم بتضمين ملفات BasePizza و Toppings. ستتلقى مدخلاً واحدًا: قائمة مفصولة بفاصلة من الإضافات المراد إضافتها (على سبيل المثال، "cheese,pepperoni" أو "mushroom,cheese,cheese").

    ابدأ بـ MargheritaPizza. لكل إضافة في المدخلات، قم بتغليف البيتزا الخاصة بك بالمزين المناسب:

    • "cheese" يغلف بـ CheeseTopping
    • "pepperoni" يغلف بـ PepperoniTopping
    • "mushroom" يغلف بـ MushroomTopping

    بعد تطبيق جميع الإضافات، اطبع وصف البيتزا النهائي وسعرها بهذا التنسيق:

    [description] = $[price]

    قم بتنسيق السعر إلى منزلتين عشريتين.

لاحظ كيف يمكن تطبيق نفس الإضافة عدة مرات — طلب جبنة مزدوجة يعني ببساطة التغليف بـ CheeseTopping مرتين. هذه المرونة هي قوة نمط المزين!

ورقة مرجعية

يُعد نمط المزين (Decorator Pattern) نمط تصميم هيكلي يضيف سلوكيات إلى الكائنات ديناميكيًا عن طريق تغليفها في كائنات مزينة في وقت التشغيل.

يقوم كل من الكائن الأساسي والمزينات بتنفيذ نفس الواجهة (interface):

<?php
interface Coffee {
    public function getDescription(): string;
    public function getCost(): float;
}

class SimpleCoffee implements Coffee {
    public function getDescription(): string {
        return "Simple Coffee";
    }
    
    public function getCost(): float {
        return 2.00;
    }
}

تقوم المزينات بتغليف كائن آخر من نفس نوع الواجهة:

<?php
class MilkDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Milk";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.50;
    }
}

class SugarDecorator implements Coffee {
    public function __construct(private Coffee $coffee) {}
    
    public function getDescription(): string {
        return $this->coffee->getDescription() . ", Sugar";
    }
    
    public function getCost(): float {
        return $this->coffee->getCost() + 0.25;
    }
}

قم بربط المزينات لدمج السلوكيات:

$coffee = new SimpleCoffee();
$coffee = new MilkDecorator($coffee);
$coffee = new SugarDecorator($coffee);

echo $coffee->getDescription() . " = $" . $coffee->getCost();
// Output: Simple Coffee, Milk, Sugar = $2.75

يمكن تطبيق المزينات عدة مرات وبأي ترتيب دون الحاجة لإنشاء فئات (classes) لكل مجموعة احتمالات.

جرّب بنفسك

<?php
require_once 'BasePizza.php';
require_once 'Toppings.php';

// قراءة المدخلات - قائمة من الإضافات مفصولة بفاصلة
$input = trim(fgets(STDIN));
$toppings = explode(',', $input);

// البدء ببيتزا مارجريتا أساسية
$pizza = new MargheritaPizza();

// TODO: قم بالمرور على كل إضافة وتغليف البيتزا بالمزين (decorator) المناسب
// - "cheese" -> التغليف بـ CheeseTopping
// - "pepperoni" -> التغليف بـ PepperoniTopping
// - "mushroom" -> التغليف بـ MushroomTopping

// TODO: اطبع النتيجة بالتنسيق التالي: [description] = $[price]
// قم بتنسيق السعر إلى منزلتين عشريتين باستخدام number_format()

?>
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming