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

نمط المزين

جزء من قسم Object Oriented Programming في رحلة C# على Coddy — الدرس 58 من 70.

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

يعمل هذا النمط من خلال جعل المزينين (decorators) ينفذون نفس الواجهة (interface) التي ينفذها الكائن الذي يغلفونه. يحتفظ كل مزين بمرجع للكائن المغلف ويفوض الاستدعاءات إليه، مع إضافة سلوكه الخاص قبل أو بعد ذلك:

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). يركز كل مزيّن على اهتمام واحد، وتقوم بدمجها حسب الحاجة لبناء السلوك الدقيق الذي تريده.

challenge icon

التحدي

سهل

لنقم ببناء نظام لطلب البيتزا باستخدام نمط المزين (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

يوضح هذا مرونة نمط المزين - حيث يغلف كل مزين أي بيتزا يتلقاها، سواء كانت بيتزا سادة أو بيتزا مزينة بالفعل، مما يسمح لك بتكديس السلوكيات ديناميكيًا!

ورقة مرجعية

نمط المزخرف (Decorator pattern) هو نمط هيكلي يضيف سلوكيات جديدة للكائنات عن طريق تغليفها في كائنات مزخرفة خاصة. يضيف وظائف بشكل ديناميكي في وقت التشغيل دون تعديل الفئة الأصلية.

تقوم المزخرفات بتنفيذ نفس الواجهة التي ينفذها الكائن الذي تغلفه، وتحتفظ بمرجع للكائن المغلف، وتفوض الاستدعاءات إليه مع إضافة سلوكها الخاص:

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;
}

يمكن تكديس المزخرفات عن طريق تغليف كائن مزخرف بمزخرف آخر:

ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new MilkDecorator(coffee);  // حليب مضاعف!

Console.WriteLine(coffee.GetDescription());  // Coffee, Milk, Milk
Console.WriteLine(coffee.GetCost());         // 3.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: طباعة الوصف النهائي والسعر
    }
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming