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

نمط المزين

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

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.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