نمط المزين
جزء من قسم البرمجة كائنية التوجه في رحلة PHP على Coddy. الدرس 77 من 91.
إن نمط Decorator هو نمط تصميم هيكلي يتيح لك إضافة سلوكيات جديدة إلى الكائنات ديناميكيًا من خلال تغليفها في كائنات تغليف خاصة تُسمى decorators. وعلى عكس الوراثة، التي تضيف السلوك وقت الترجمة، تضيف decorators السلوك وقت التشغيل.
تخيّل متجرًا لـ coffee. تبدأ بـ coffee أساسي، ثم تضيف إضافات مثل الحليب أو السكر أو الكريمة المخفوقة.
كل إضافة تُغلّف القهوة الأصلية وتضيف إلى وصفها وتكلفتها. وتتمثل الفكرة الأساسية في أن كلًا من القهوة الأساسية وdecorators يشتركان في الواجهة نفسها.
<?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();
Output:
Simple Coffee, Milk, Sugar = $2.75يلتف كل decorator حول الكائن السابق، مُشكِّلًا سلسلة. يتيح لك هذا النمط دمج السلوكيات بأي ترتيب وبأي عدد دون إنشاء فئة لكل تركيبة ممكنة. هل تحتاج إلى كمية مضاعفة من Milk؟ ما عليك سوى تغليفه مرتين.
التحدي
سهللنَبْنِ نظامًا لطلب البيتزا باستخدام نمط Decorator. ستبدأ ببيتزا أساسية وتضيف الإضافات ديناميكيًا: كل إضافة تغلّف البيتزا وadds إلى description وprice الخاصين بها، تمامًا كما في مثال coffee من الدرس.
ستنظّم التعليمات البرمجية عبر أربعة ملفات:
Pizza.php: Define واجهةPizzaالتي تحدد العقد لجميع أنواع البيتزا والإضافات. يجب أن تحتوي على طريقتين:getDescription(): stringوgetPrice(): float.BasePizza.php: تضمين واجهة Pizza وإنشاء فئةMargheritaPizzaالتي implements إياها. هذه هي البيتزا الأساسية، مع description بقيمة"Margherita"وprice بقيمة8.00.Toppings.php: تضمين واجهة Pizza وإنشاء ثلاث فئات decorator التي implements إياها. كل decorator يغلّف كائنPizza(يتم Accept الكائن في constructor) ويعزّز كلًا من description وprice:CheeseTopping: يضيف", Extra Cheese"إلى description و1.50إلى pricePepperoniTopping: يضيف", Pepperoni"إلى description و2.00إلى priceMushroomTopping: يضيف", Mushrooms"إلى description و1.25إلى price
main.php: تضمين الملفين BasePizza وToppings. ستتلقى input واحدًا: قائمة إضافات مفصولة بفواصل لإضافتها (مثلًا"cheese,pepperoni"أو"mushroom,cheese,cheese").ابدأ باستخدام
MargheritaPizza. ولكل إضافة في input، غلّف البيتزا باستخدام decorator المناسب:"cheese"تُغلّف باستخدامCheeseTopping"pepperoni"تُغلّف باستخدامPepperoniTopping"mushroom"تُغلّف باستخدامMushroomTopping
بعد تطبيق جميع الإضافات، اطبع description وprice النهائيين للبيتزا بهذا التنسيق:
[description] = $[price]نسّق price ليحتوي على منزلتين عشريتين.
لاحظ كيف يمكن تطبيق الإضافة نفسها عدة مرات: طلب جبنة مضاعفة يعني ببساطة التغليف باستخدام CheeseTopping مرتين. هذه المرونة هي قوة نمط Decorator!
جرّب بنفسك
<?php
require_once 'BasePizza.php';
require_once 'Toppings.php';
// اقرأ المدخلات - قائمة إضافات مفصولة بفواصل
$input = trim(fgets(STDIN));
$toppings = explode(',', $input);
// ابدأ بقاعدة MargheritaPizza
$pizza = new MargheritaPizza();
// TODO: مر عبر كل إضافة وغلّف البيتزا بالمزخرف المناسب
// - "cheese" -> غلّف بـ CheeseTopping
// - "pepperoni" -> غلّف بـ PepperoniTopping
// - "mushroom" -> غلّف بـ MushroomTopping
// TODO: اطبع النتيجة بالتنسيق: [description] = $[price]
// نسّق السعر إلى منزلتين عشريتين باستخدام number_format()
?>يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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)تدرّب بنفسك: مترجم PHP عبر الإنترنت