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

نمط المزين

جزء من قسم Object Oriented Programming في رحلة Python على Coddy — الدرس 51 من 64.

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

إليك مثال بسيط على القهوة:

class Coffee:
    def cost(self):
        return 5
    
    def description(self):
        return "Simple coffee"

قم بإنشاء مزخرفات (decorators) تضيف ميزات إلى القهوة:

class MilkDecorator:
    def __init__(self, coffee):
        self.coffee = coffee
    
    def cost(self):
        return self.coffee.cost() + 2
    
    def description(self):
        return self.coffee.description() + " + Milk"

class SugarDecorator:
    def __init__(self, coffee):
        self.coffee = coffee
    
    def cost(self):
        return self.coffee.cost() + 1
    
    def description(self):
        return self.coffee.description() + " + Sugar"

يقوم كل ديكوريتور (decorator) بتغليف كائن آخر وإضافة وظائفه الخاصة.

استخدم المزينات (decorators) لبناء قهوة مخصصة:

# ابدأ بقهوة أساسية
my_coffee = Coffee()
print(f"{my_coffee.description()}: ${my_coffee.cost()}")

# أضف الحليب
my_coffee = MilkDecorator(my_coffee)
print(f"{my_coffee.description()}: ${my_coffee.cost()}")

# أضف السكر
my_coffee = SugarDecorator(my_coffee)
print(f"{my_coffee.description()}: ${my_coffee.cost()}")

قم بإنشاء مثال آخر مع تنسيق النص:

class Text:
    def __init__(self, content):
        self.content = content
    
    def render(self):
        return self.content

class BoldDecorator:
    def __init__(self, text):
        self.text = text
    
    def render(self):
        return f"<b>{self.text.render()}</b>"

class ItalicDecorator:
    def __init__(self, text):
        self.text = text
    
    def render(self):
        return f"<i>{self.text.render()}</i>"

# بناء نص منسق خطوة بخطوة
message = Text("Hello World")
message = BoldDecorator(message)
message = ItalicDecorator(message)

print(message.render())

المخرجات:

Simple coffee: $5
Simple coffee + Milk: $7
Simple coffee + Milk + Sugar: $8
<i><b>Hello World</b></i>

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

challenge icon

التحدي

متوسط

في هذا التحدي، ستقوم بتنفيذ نمط التصميم Decorator لإنشاء نظام مرن لطلب القهوة لمقهى. يسمح نمط Decorator بإضافة سلوك إلى كائنات فردية بشكل ديناميكي، دون التأثير على سلوك الكائنات الأخرى من نفس الفئة.

أنت تقوم ببناء نظام لمقهى يحتاج إلى:

  • تقديم أنواع مختلفة من المشروبات ("Espresso"، "Dark Roast"، "House Blend"، "Decaf")
  • السماح للعملاء بإضافة إضافات ("Milk"، "Mocha"، "Soy"، "Whip") إلى مشروباتهم
  • حساب تكلفة المشروبات مع الإضافات المضافة
  1. أكمل فئة Beverage المجردة وتنفيذاتها الملموسة في beverage.py
  2. قم بتنفيذ فئة CondimentDecorator المجردة والمزخرفات الملموسة في condiment_decorator.py
  3. قم بتنفيذ سيناريوهات الاختبار في driver.py للتحقق من تنفيذك

ورقة مرجعية

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

الهيكل الأساسي مع مثال القهوة:

class Coffee:
    def cost(self):
        return 5
    
    def description(self):
        return "Simple coffee"

إنشاء مزينات (decorators) تقوم بتغليف وتوسيع الوظائف:

class MilkDecorator:
    def __init__(self, coffee):
        self.coffee = coffee
    
    def cost(self):
        return self.coffee.cost() + 2
    
    def description(self):
        return self.coffee.description() + " + Milk"

class SugarDecorator:
    def __init__(self, coffee):
        self.coffee = coffee
    
    def cost(self):
        return self.coffee.cost() + 1
    
    def description(self):
        return self.coffee.description() + " + Sugar"

ربط المزينات لبناء كائنات مخصصة:

# ابدأ بقهوة بسيطة
my_coffee = Coffee()

# أضف المزينات
my_coffee = MilkDecorator(my_coffee)
my_coffee = SugarDecorator(my_coffee)

print(f"{my_coffee.description()}: ${my_coffee.cost()}")
# Output: Simple coffee + Milk + Sugar: $8

مثال على تنسيق النص:

class Text:
    def __init__(self, content):
        self.content = content
    
    def render(self):
        return self.content

class BoldDecorator:
    def __init__(self, text):
        self.text = text
    
    def render(self):
        return f"<b>{self.text.render()}</b>"

class ItalicDecorator:
    def __init__(self, text):
        self.text = text
    
    def render(self):
        return f"<i>{self.text.render()}</i>"

# ربط المزينات
message = Text("Hello World")
message = BoldDecorator(message)
message = ItalicDecorator(message)

print(message.render())
# Output: <i><b>Hello World</b></i>

الفوائد الرئيسية: يسمح بتركيبات مرنة للميزات دون إنشاء العديد من الفئات الفرعية (subclasses)، ويحافظ على مرجع للكائن المغلف، ويضيف الوظائف بشكل ديناميكي.

جرّب بنفسك

# استيراد جميع الفئات اللازمة
from beverage import Espresso, HouseBlend, DarkRoast, Decaf
from condiment_decorator import Milk, Mocha, Soy, Whip

# معالج حالات الاختبار الشامل
test_case = input()

if test_case == "basic_beverage_test":
    # اختبار وظائف المشروبات الأساسية
    espresso = Espresso()
    print(f"Description: {espresso.get_description()}")
    print(f"Cost: ${espresso.cost():.2f}")

elif test_case == "all_beverages_test":
    # اختبار جميع أنواع المشروبات
    beverages = [Espresso(), HouseBlend(), DarkRoast(), Decaf()]
    for beverage in beverages:
        print(f"{beverage.get_description()}: ${beverage.cost():.2f}")

elif test_case == "single_condiment_test":
    # اختبار تزيين بمكون إضافي واحد
    espresso = Espresso()
    espresso_with_milk = Milk(espresso)
    print(f"Description: {espresso_with_milk.get_description()}")
    print(f"Cost: ${espresso_with_milk.cost():.2f}")

elif test_case == "multiple_condiments_test":
    # اختبار تزيين بمكونات إضافية متعددة
    house_blend = HouseBlend()
    decorated_coffee = Mocha(Soy(Whip(house_blend)))
    print(f"Description: {decorated_coffee.get_description()}")
    print(f"Cost: ${decorated_coffee.cost():.2f}")

elif test_case == "all_condiments_test":
    # اختبار جميع المكونات الإضافية على مشروب واحد
    dark_roast = DarkRoast()
    fully_loaded = Milk(Mocha(Soy(Whip(dark_roast))))
    print(f"Description: {fully_loaded.get_description()}")
    print(f"Cost: ${fully_loaded.cost():.2f}")

elif test_case == "cost_calculation_test":
    # اختبار حسابات التكلفة لمجموعات مختلفة
    espresso = Espresso()
    
    # إضافات فردية
    with_milk = Milk(espresso)
    with_mocha = Mocha(espresso)
    
    print(f"Espresso: ${espresso.cost():.2f}")
    print(f"Espresso + Milk: ${with_milk.cost():.2f}")
    print(f"Espresso + Mocha: ${with_mocha.cost():.2f}")

elif test_case == "decoration_order_test":
    # اختبار أن ترتيب التزيين لا يؤثر على النتيجة النهائية
    decaf = Decaf()
    
    order1 = Milk(Mocha(decaf))
    order2 = Mocha(Milk(decaf))
    
    print(f"Order 1 - {order1.get_description()}: ${order1.cost():.2f}")
    print(f"Order 2 - {order2.get_description()}: ${order2.cost():.2f}")
    print(f"Same cost: {order1.cost() == order2.cost()}")

elif test_case == "complex_order_test":
    # اختبار طلبات المشروبات المعقدة
    orders = [
        Mocha(Whip(Espresso())),
        Soy(Milk(HouseBlend())),
        Whip(Mocha(Soy(DarkRoast()))),
        Milk(Decaf())
    ]
    
    for i, order in enumerate(orders, 1):
        print(f"Order {i}: {order.get_description()}")
        print(f"Cost: ${order.cost():.2f}")
        print("---")

elif test_case == "double_condiment_test":
    # اختبار إضافة نفس المكون الإضافي عدة مرات
    espresso = Espresso()
    double_mocha = Mocha(Mocha(espresso))
    print(f"Description: {double_mocha.get_description()}")
    print(f"Cost: ${double_mocha.cost():.2f}")

elif test_case == "beverage_comparison_test":
    # مقارنة تكاليف المشروبات المختلفة مع نفس المكونات الإضافية
    base_beverages = [Espresso(), HouseBlend(), DarkRoast(), Decaf()]
    
    print("All beverages with Milk + Mocha:")
    for beverage in base_beverages:
        decorated = Milk(Mocha(beverage))
        print(f"{decorated.get_description()}: ${decorated.cost():.2f}")

elif test_case == "condiment_cost_test":
    # اختبار تكاليف المكونات الإضافية الفردية
    espresso = Espresso()
    base_cost = espresso.cost()
    
    condiments = [
        ("Milk", Milk(espresso)),
        ("Mocha", Mocha(espresso)),
        ("Soy", Soy(espresso)),
        ("Whip", Whip(espresso))
    ]
    
    for name, decorated in condiments:
        additional_cost = decorated.cost() - base_cost
        print(f"{name} adds: ${additional_cost:.2f}")

elif test_case == "cheapest_most_expensive_test":
    # العثور على أرخص وأغلى المشروبات الأساسية
    beverages = [
        ("Espresso", Espresso()),
        ("House Blend", HouseBlend()),
        ("Dark Roast", DarkRoast()),
        ("Decaf", Decaf())
    ]
    
    beverages.sort(key=lambda x: x[1].cost())
    print(f"Cheapest: {beverages[0][0]} - ${beverages[0][1].cost():.2f}")
    print(f"Most Expensive: {beverages[-1][0]} - ${beverages[-1][1].cost():.2f}")

elif test_case == "nested_decoration_test":
    # اختبار التزيينات المتداخلة بعمق
    beverage = HouseBlend()
    # إضافة 5 طبقات من التزيينات
    decorated = Whip(Soy(Mocha(Milk(Whip(beverage)))))
    print(f"Description: {decorated.get_description()}")
    print(f"Cost: ${decorated.cost():.2f}")
    
    # عد عدد المكونات الإضافية
    description = decorated.get_description()
    condiment_count = description.count(" + ")
    print(f"Number of condiments: {condiment_count}")

elif test_case == "cost_breakdown_test":
    # تفصيل التكلفة المفصل
    dark_roast = DarkRoast()
    print(f"Base Dark Roast: ${dark_roast.cost():.2f}")
    
    with_soy = Soy(dark_roast)
    print(f"+ Soy: ${with_soy.cost():.2f}")
    
    with_mocha = Mocha(with_soy)
    print(f"+ Mocha: ${with_mocha.cost():.2f}")
    
    with_whip = Whip(with_mocha)
    print(f"+ Whip: ${with_whip.cost():.2f}")
    
    final_cost = with_whip.cost()
    print(f"Final total: ${final_cost:.2f}")

elif test_case == "description_format_test":
    # اختبار اتساق تنسيق الوصف
    espresso = Espresso()
    test_combinations = [
        Milk(espresso),
        Mocha(Milk(espresso)),
        Whip(Mocha(Milk(espresso))),
        Soy(Whip(Mocha(Milk(espresso))))
    ]
    
    for combo in test_combinations:
        description = combo.get_description()
        print(f"Description: '{description}'")
        # التحقق مما إذا كان الوصف يبدأ باسم المشروب الأساسي
        starts_correctly = description.startswith("Espresso")
        print(f"Starts with base name: {starts_correctly}")
        print("---")
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming