نمط المزين
جزء من قسم 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). يركز كل مزيّن على اهتمام واحد، وتقوم بدمجها حسب الحاجة لبناء السلوك الدقيق الذي تريده.
التحدي
سهللنقم ببناء نظام لطلب البيتزا باستخدام نمط المزين (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: طباعة الوصف النهائي والسعر
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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