نمط المزين
جزء من قسم Object Oriented Programming في رحلة C# على Coddy — الدرس 58 من 70.
إن نمط المزخرف (Decorator pattern) هو نمط هيكلي يتيح لك إرفاق سلوكيات جديدة للكائنات عن طريق تغليفها في كائنات خاصة تسمى المزخرفات (decorators). وعلى عكس الوراثة، التي تضيف السلوك في وقت التجميع (compile time)، تضيف المزخرفات وظائف بشكل ديناميكي في وقت التشغيل (runtime) دون تعديل الفئة (class) الأصلية.
يعمل النمط من خلال جعل الـ decorators ينفذون نفس الواجهة التي ينفذها الكائن الذي يقومون بتغليفه. يحتفظ كل decorator بمرجع للكائن المغلف ويفوض الاستدعاءات إليه، مضيفاً سلوكه الخاص قبل أو بعد ذلك:
public interface ICoffee
{
string GetDescription();
decimal GetCost();
}
public class SimpleCoffee : ICoffee
{
public string GetDescription() => "Coffee";
public decimal GetCost() => 2.00m;
}
public abstract class CoffeeDecorator : ICoffee
{
protected ICoffee _coffee;
public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
public virtual string GetDescription() => _coffee.GetDescription();
public virtual decimal GetCost() => _coffee.GetCost();
}
public class MilkDecorator : CoffeeDecorator
{
public MilkDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription() => _coffee.GetDescription() + ", Milk";
public override decimal GetCost() => _coffee.GetCost() + 0.50m;
}تكمن قوة المزخرفات (decorators) في قدرتها على التراكم. يمكنك تغليف كائن مزخرف بمزخرف آخر:
ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new MilkDecorator(coffee); // حليب مضاعف!
Console.WriteLine(coffee.GetDescription()); // قهوة، حليب، حليب
Console.WriteLine(coffee.GetCost()); // 3.00يعد نمط المزين (Decorator pattern) مثالياً عندما تحتاج إلى إضافة مسؤوليات إلى الكائنات دون التسبب في تضخم هائل في عدد الفئات الفرعية (subclasses). يركز كل مزين على اهتمام واحد، ويمكنك دمجها حسب الحاجة لبناء السلوك الدقيق الذي تريده.
التحدي
سهللنقم ببناء نظام لطلب البيتزا باستخدام نمط المزين (Decorator pattern). ستقوم بإنشاء بيتزا أساسية ثم إضافة الإضافات إليها ديناميكيًا، حيث تقوم كل إضافة بتغليف الطبقة السابقة وتضيف إلى كل من الوصف والسعر.
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
Pizza.cs: قم بتعريف واجهةIPizzaفي مساحة الأسماءPizzaShopمع طريقتين:GetDescription()التي تعيد سلسلة نصية (string) وGetPrice()التي تعيد قيمة عشرية (decimal). ثم أنشئ فئةPlainPizzaالتي تنفذ هذه الواجهة - يجب أن تعيد"Pizza"كوصف لها و8.00mكسعر أساسي لها.ToppingDecorators.cs: أنشئ فئة مجردةToppingDecoratorتنفذIPizzaوتحتفظ بمرجع للبيتزا المغلفة (يتم تمريرها عبر المنشئ). يجب أن تقوم طرقها الافتراضية (virtual methods) بالتفويض إلى البيتزا المغلفة. ثم أنشئ ثلاثة مزينين ملموسين (concrete decorators):CheeseTopping- يضيف", Cheese"إلى الوصف ويضيف1.50mإلى السعرPepperoniTopping- يضيف", Pepperoni"إلى الوصف ويضيف2.00mإلى السعرMushroomTopping- يضيف", Mushrooms"إلى الوصف ويضيف1.25mإلى السعر
Program.cs: اجمع كل شيء معًا بالبدء ببيتزا سادة وتغليفها بالإضافات بناءً على المدخلات. يقوم كل مزين إضافات بتغليف البيتزا السابقة، مما يبني الطلب النهائي طبقة تلو الأخرى.
ستتلقى مدخلاً واحدًا:
- قائمة مفصولة بفاصلة من الإضافات المراد إضافتها (على سبيل المثال،
cheese,pepperoni,cheese)
ابدأ بـ PlainPizza، ثم غلفها بالمزين المناسب لكل إضافة بالترتيب الذي تظهر به. الإضافات الصالحة هي cheese و pepperoni و mushrooms. بعد تطبيق جميع الإضافات، اطبع الوصف في سطر واحد والسعر الإجمالي في السطر التالي.
على سبيل المثال، إذا كان المدخل هو cheese,pepperoni,mushrooms، يجب أن يكون المخرج:
Pizza, Cheese, Pepperoni, Mushrooms
12.75لاحظ كيف يمكنك إضافة نفس الإضافة عدة مرات! إذا كان المدخل هو cheese,cheese، فستحصل على:
Pizza, Cheese, Cheese
11.00يوضح هذا مرونة نمط المزين - حيث يقوم كل مزين بتغليف أي بيتزا يتلقاها، سواء كانت بيتزا سادة أو بيتزا مزينة بالفعل، مما يسمح لك بتكديس السلوكيات ديناميكيًا!
جرّب بنفسك
using System;
using PizzaShop;
class Program
{
public static void Main(string[] args)
{
// قراءة قائمة الإضافات المفصولة بفواصل
string input = Console.ReadLine();
string[] toppings = input.Split(',');
// TODO: ابدأ بـ PlainPizza
// TODO: قم بالمرور عبر كل إضافة وتغليف البيتزا
// باستخدام الـ decorator المناسب بناءً على اسم الإضافة:
// - "cheese" -> CheeseTopping
// - "pepperoni" -> PepperoniTopping
// - "mushrooms" -> MushroomTopping
// TODO: طباعة الوصف النهائي والسعر
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات OOP
الملفات الخارجيةNamespaces والتوجيهاتمقدمة في Classes و Objectsالكلمة المفتاحية 'this'Methods والمعاملاتFields مقابل PropertiesConstructorsObject Initializersمراجعة - آلة حاسبة بسيطة4الوراثة
صيغة الوراثة الأساسية (:)الكلمة المفتاحية 'base'الكلمات المفتاحية Virtual و Overrideالأصناف المختومة (Sealed Classes)الفئة الأساسية 'object'مراجعة - هيكلية الموظفين7ميزات متقدمة
تحميل العوامل (Operator Overloading)المفهرسات (Indexers (this[]))إعادة تعريف ()ToStringدوال التوسعة (Extension Methods)ملخص - Custom List2الخصائص والأعضاء الساكنة (Static Members)
الخصائص المطبقة تلقائيًا (Auto-Implemented Properties)خصائص القراءة فقط والكتابة فقطالحقول والدوال الساكنة (Static)الفئات الساكنة (Static Classes)الأعضاء ذات البنية التعبيرية (Expression-Bodied Members)5تعدد الأشكال والواجهات (Polymorphism & Interfaces)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالواجهة مقابل الفئة المجردة (Abstract Class)الواجهات المتعددةالواجهات الصريحةUpcasting و Downcastingملخص - حاسبة الأشكال8مفاهيم البرمجة كائنية التوجه (OOP) المتقدمة
التركيب بدلاً من الوراثةالـ Generics (الأصناف والدوال)الـ Delegates والأحداثالـ Attributes والـ Reflectionواجهة IDisposable وجملة usingأساسيات Dependency Injection