نمط المزين
جزء من قسم 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) لكل مجموعة محتملة. هل تحتاج إلى حليب مضاعف؟ فقط قم بتغليفه مرتين.
التحدي
سهللنقم ببناء نظام لطلب البيتزا باستخدام نمط المزين (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()
?>يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات OOP
الملفات الخارجيةمقدمة في OOPClasses مقابل Objectsالكلمة المفتاحية $thisالـ Methodsالـ Propertiesالمنشئ (__construct)الهادم (__destruct)مراجعة - آلة حاسبة بسيطة4الوراثة
الوراثة الأساسيةالكلمة المفتاحية parent::إعادة تعريف الدوال (Method Overriding)الكلمة المفتاحية finalالأصناف المجردة (Abstract Classes)مراجعة - هيكلية الموظفين7التغليف (Encapsulation)
العام، المحمي، الخاص (Public, Protected, Private)نظرة متعمقة على معدلات الوصولدوال Getters و Settersإخفاء المعلوماتميزة Constructor Promotion (8.0)مراجعة - نظام سجلات الطلاب10مفاهيم البرمجة كائنية التوجه المتقدمة
التركيب مقابل الوراثةحقن التبعيةالأصناف المجهولةالتعدادات (PHP 8.1)الألياف (PHP 8.1)تعمق في استنساخ الكائناتالمولدات والمكررات13أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبنمط المستودع2مساحات الأسماء والتحميل التلقائي
مقدمة إلى مساحات الأسماءالكلمة المفتاحية useمعيار التحميل التلقائي PSR-4أداة التحميل التلقائي Composerملخص - مشروع منظم5الواجهات والعقود
مقدمة إلى الواجهاتتنفيذ الواجهاتتنفيذ واجهات متعددةالواجهة مقابل الـ Abstract Classالـ Type Hinting مع الواجهاتملخص - حاسبة الأشكال8الميثودات السحرية (Magic Methods)
مقدمة في الميثودات السحرية__toString و __debugInfo__get, __set, __isset, __unset__call و __callStatic__clone واستنساخ الكائنات__serialize و __unserializeمراجعة - Custom Collection3خصائص الكلاس
خصائص الـ Instance مقابل الخصائص الساكنة (Static)الثوابت في الكلاساتالدوال والخصائص الساكنة (Static)الخصائص الخاصة (Private) والمحمية (Protected)خصائص للقراءة فقط (Readonly) في PHP 8.1مراجعة - مدير الحساب البنكي6تعدد الأشكال (Polymorphism)
مراجعة إعادة تعريف الدوال (Method Overriding)تعدد الأشكال عبر الـ Interfacesتلميح النوع (Type Hinting) وأنواع الاتحاد (Union Types)الربط الاستاتيكي المتأخر (Late Static Binding)ملخص - معالج الدفع (Payment Processor)