Menu
Coddy logo textTech

Wzorzec dekoratora

Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 98 z 104.

Wzorzec Dekoratora pozwala dynamicznie dodawać nowe zachowania do obiektów, opakowując je w specjalne obiekty nazywane dekoratorami. W przeciwieństwie do dziedziczenia, które dodaje zachowanie podczas kompilacji, dekoratory pozwalają rozszerzać funkcjonalność w czasie działania bez modyfikowania oryginalnej klasy.

Wzorzec działa dzięki temu, że zarówno oryginalny obiekt, jak i dekoratory implementują ten sam interfejs. Każdy dekorator przechowuje referencję do komponentu i dodaje własne zachowanie przed przekazaniem wywołania do opakowanego obiektu lub po nim:

#include <iostream>
#include <memory>

// Interfejs komponentu
class Coffee {
public:
    virtual std::string getDescription() const = 0;
    virtual double getCost() const = 0;
    virtual ~Coffee() = default;
};

// Konkretny komponent
class SimpleCoffee : public Coffee {
public:
    std::string getDescription() const override { return "Coffee"; }
    double getCost() const override { return 2.0; }
};

// Dekorator bazowy
class CoffeeDecorator : public Coffee {
protected:
    std::unique_ptr<Coffee> coffee;
public:
    CoffeeDecorator(std::unique_ptr<Coffee> c) : coffee(std::move(c)) {}
};

// Konkretne dekoratory
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; }
};

Dekoratory można nakładać jeden na drugi — każdy opakowuje poprzedni, dodając kolejne warstwy funkcjonalności:

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();
    // Wynik: Coffee, Milk, Sugar: $2.7
}

Użyj wzorca Dekorator, gdy potrzebujesz dynamicznie dodawać obiektom obowiązki bez wpływu na inne obiekty lub gdy rozszerzanie funkcjonalności przez dziedziczenie prowadziłoby do eksplozji liczby klas.

challenge icon

Wyzwanie

Łatwy

Zbudujmy System zamawiania pizzy przy użyciu wzorca Dekorator. Stworzysz elastyczny system, w którym klienci mogą zacząć od podstawowej pizzy i dodać różne dodatki: każdy dodatek opakowuje poprzednie zamówienie i dodaje własny koszt oraz opis. To idealne zastosowanie dekoratorów: dodatki można łączyć w dowolnej konfiguracji bez tworzenia ogromnej liczby podklas.

Rozmieścisz kod w trzech plikach:

  • Pizza.h: Zdefiniuj interfejs komponentu i klasę podstawowej pizzy.

    Utwórz abstrakcyjną klasę Pizza z czysto wirtualnymi metodami getDescription() (zwraca ciąg znaków) i getCost() (zwraca liczbę typu double), a także wirtualnym destruktorem.

    Następnie utwórz konkretną klasę PlainPizza, która reprezentuje podstawową pizzę. Powinna zwracać "Pizza" jako opis i mieć koszt podstawowy równy 8.0.

  • ToppingDecorator.h: Utwórz bazową klasę dekoratora i konkretne dekoratory dodatków.

    Utwórz klasę ToppingDecorator, która dziedziczy po Pizza i przechowuje std::unique_ptr<Pizza> do opakowywanego komponentu. Będzie ona klasą bazową dla wszystkich dekoratorów dodatków.

    Zaimplementuj trzy konkretne dekoratory dodatków:

    • CheeseTopping: dodaje ", Cheese" do opisu i 1.5 do kosztu
    • PepperoniTopping: dodaje ", Pepperoni" do opisu i 2.0 do kosztu
    • MushroomTopping: dodaje ", Mushrooms" do opisu i 1.0 do kosztu

    Każdy dekorator powinien delegować wywołanie do opakowanej pizzy, a następnie dodawać własny wkład.

  • main.cpp: Utwórz spersonalizowane zamówienie pizzy na podstawie danych wprowadzonych przez użytkownika.

    Odczytaj trzy dane wejściowe (każda z nich będzie miała wartość yes lub no):

    1. Dodać ser?
    2. Dodać pepperoni?
    3. Dodać pieczarki?

    Zacznij od PlainPizza, a następnie opakuj ją odpowiednimi dekoratorami dodatków na podstawie wprowadzonych danych (w kolejności: ser, pepperoni, pieczarki). Na koniec wyświetl szczegóły zamówienia w następującym formacie:

    Order: [description]
    Total: $[cost]

Na przykład, dla danych wejściowych yes, yes, no:

Order: Pizza, Cheese, Pepperoni
Total: $11.5

Dla danych wejściowych yes, no, yes:

Order: Pizza, Cheese, Mushrooms
Total: $10.5

Dla danych wejściowych no, no, no:

Order: Pizza
Total: $8

Dla danych wejściowych yes, yes, yes:

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

Zauważ, jak każdy dekorator opakowuje poprzedni, stopniowo rozbudowując opis i koszt. Możesz łatwo dodawać nowe dodatki, tworząc nowe klasy dekoratorów, bez modyfikowania istniejącego kodu pizzy ani dodatków: na tym polega piękno wzorca Dekorator.

Spróbuj swoich sił

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

using namespace std;

int main() {
    // Wczytaj trzy odpowiedzi (tak lub nie)
    string addCheese, addPepperoni, addMushrooms;
    cin >> addCheese;
    cin >> addPepperoni;
    cin >> addMushrooms;
    
    // TODO: Zacznij od PlainPizza, używając std::unique_ptr
    
    // TODO: Jeśli addCheese ma wartość "yes", opakuj pizzę w CheeseTopping
    
    // TODO: Jeśli addPepperoni ma wartość "yes", opakuj pizzę w PepperoniTopping
    
    // TODO: Jeśli addMushrooms ma wartość "yes", opakuj pizzę w MushroomTopping
    
    // TODO: Wypisz zamówienie w formacie:
    // Order: [description]
    // Total: $[cost]
    
    return 0;
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C++ online