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

نمط المزين

جزء من قسم البرمجة كائنية التوجه في رحلة C++ على Coddy. الدرس 98 من 104.

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

يعمل النمط من خلال جعل كلٍّ من الكائن الأصلي وdecorators ينفّذان الواجهة نفسها. يحتفظ كل decorator بمرجع إلى component ويضيف سلوكه الخاص قبل تفويض المهمة إلى الكائن المُغلَّف أو بعده:

#include <iostream>
#include <memory>

// واجهة المكون
class Coffee {
public:
    virtual std::string getDescription() const = 0;
    virtual double getCost() const = 0;
    virtual ~Coffee() = default;
};

// المكون الملموس
class SimpleCoffee : public Coffee {
public:
    std::string getDescription() const override { return "Coffee"; }
    double getCost() const override { return 2.0; }
};

// المزخرف الأساسي
class CoffeeDecorator : public Coffee {
protected:
    std::unique_ptr<Coffee> coffee;
public:
    CoffeeDecorator(std::unique_ptr<Coffee> c) : coffee(std::move(c)) {}
};

// المزخرفات الملموسة
class MilkDecorator : public CoffeeDecorator {
public:
    MilkDecorator(std::unique_ptr<Coffee> c) : CoffeeDecorator(std::move(c)) {}
    std::string getDescription() const override {
        return coffee->getDescription() + ", Milk";
    }
    double getCost() const override { return coffee->getCost() + 0.5; }
};

class SugarDecorator : public CoffeeDecorator {
public:
    SugarDecorator(std::unique_ptr<Coffee> c) : CoffeeDecorator(std::move(c)) {}
    std::string getDescription() const override {
        return coffee->getDescription() + ", Sugar";
    }
    double getCost() const override { return coffee->getCost() + 0.2; }
};

يمكن تكديس decorators - إذ يغلّف كلٌّ منها العنصر السابق، مما يبني الوظائف طبقةً تلو الأخرى:

int main() {
    std::unique_ptr<Coffee> order = std::make_unique<SimpleCoffee>();
    order = std::make_unique<MilkDecorator>(std::move(order));
    order = std::make_unique<SugarDecorator>(std::move(order));
    
    std::cout << order->getDescription() << ": $" << order->getCost();
    // المخرجات: Coffee, Milk, Sugar: $2.7
}

استخدم نمط Decorator عندما تحتاج إلى إضافة مسؤوليات إلى الكائنات ديناميكيًا دون التأثير في الكائنات الأخرى، أو عندما يؤدي توسيع الوظائف من خلال التوريث من الفئات إلى انفجار في عدد الفئات.

challenge icon

التحدي

سهل

لنَبْنِ نظام طلب البيتزا باستخدام نمط Decorator. ستنشئ نظامًا مرنًا يمكن للعملاء فيه البدء ببيتزا أساسية وإضافة إضافات متنوعة: إذ تغلّف كل إضافة الطلب السابق وتضيف تكلفتها ووصفها الخاصين. هذه حالة استخدام مثالية لـ Decorator: يمكن تكديس الإضافات بأي مجموعة دون إنشاء عدد هائل من الفئات الفرعية.

ستنظّم شيفرتك عبر ثلاثة ملفات:

  • Pizza.h: عرّف واجهة المكوّن وفئة البيتزا الأساسية.

    أنشئ فئة Pizza مجردة تحتوي على أساليب افتراضية خالصة getDescription() (تُرجع سلسلة نصية) وgetCost() (تُرجع قيمة double)، بالإضافة إلى destructor افتراضي.

    ثم أنشئ فئة ملموسة PlainPizza تمثّل البيتزا الأساسية. يجب أن تُرجع "Pizza" كوصف لها، وأن تكون تكلفتها الأساسية 8.0.

  • ToppingDecorator.h: أنشئ الفئة الأساسية لـ Decorator وdecorators الإضافات الملموسة.

    أنشئ فئة ToppingDecorator ترث من Pizza وتحتفظ بمؤشر std::unique_ptr<Pizza> إلى المكوّن المغلَّف. ويُستخدم هذا كأساس لجميع decorators الإضافات.

    نفّذ ثلاثة decorators ملموسة للإضافات:

    • CheeseTopping: تضيف ", Cheese" إلى الوصف و1.5 إلى التكلفة
    • PepperoniTopping: تضيف ", Pepperoni" إلى الوصف و2.0 إلى التكلفة
    • MushroomTopping: تضيف ", Mushrooms" إلى الوصف و1.0 إلى التكلفة

    يجب أن يفوّض كل Decorator العملية إلى البيتزا المغلَّفة، ثم يضيف مساهمته الخاصة.

  • main.cpp: أنشئ طلب بيتزا مخصّصًا بناءً على إدخال المستخدم.

    اقرأ ثلاثة مدخلات (سيكون كل منها yes أو no):

    1. إضافة الجبن؟
    2. إضافة الببروني؟
    3. إضافة الفطر؟

    ابدأ بـ PlainPizza، ثم غلّفها بـ decorators الإضافات المناسبة بناءً على المدخلات (بالترتيب: الجبن، الببروني، الفطر). وأخيرًا، اطبع تفاصيل الطلب بهذا التنسيق:

    Order: [description]
    Total: $[cost]

على سبيل المثال، مع المدخلات yes وyes وno:

Order: Pizza, Cheese, Pepperoni
Total: $11.5

مع المدخلات yes وno وyes:

Order: Pizza, Cheese, Mushrooms
Total: $10.5

مع المدخلات no وno وno:

Order: Pizza
Total: $8

مع المدخلات yes وyes وyes:

Order: Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.5

لاحظ كيف يغلّف كل Decorator الـ Decorator السابق، فيبني الوصف والتكلفة معًا طبقةً بعد طبقة. يمكنك بسهولة إضافة إضافات جديدة من خلال إنشاء فئات Decorator جديدة دون تعديل شيفرة البيتزا أو الإضافات الحالية: تلك هي روعة نمط Decorator.

جرّب بنفسك

#include <iostream>
#include <string>
#include <memory>
#include "Pizza.h"
#include "ToppingDecorator.h"

using namespace std;

int main() {
    // اقرأ ثلاثة مدخلات (yes أو no)
    string addCheese, addPepperoni, addMushrooms;
    cin >> addCheese;
    cin >> addPepperoni;
    cin >> addMushrooms;
    
    // TODO: ابدأ بـ PlainPizza باستخدام std::unique_ptr
    
    // TODO: إذا كان addCheese هو "yes"، لف البيتزا بـ CheeseTopping
    
    // TODO: إذا كان addPepperoni هو "yes"، لف البيتزا بـ PepperoniTopping
    
    // TODO: إذا كان addMushrooms هو "yes"، لف البيتزا بـ MushroomTopping
    
    // TODO: اطبع الطلب بالتنسيق:
    // Order: [description]
    // Total: $[cost]
    
    return 0;
}
quiz iconاختبر نفسك

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

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم C++ عبر الإنترنت