Menu
Coddy logo textTech

Pattern Decorator

Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 98 di 104.

Il pattern Decorator ti consente di aggiungere dinamicamente nuovi comportamenti agli oggetti avvolgendoli in oggetti speciali chiamati decorator. A differenza dell'ereditarietà, che aggiunge comportamenti in fase di compilazione, i decorator ti permettono di estendere le funzionalità in fase di esecuzione senza modificare la classe originale.

Il pattern funziona facendo implementare la stessa interfaccia sia all'oggetto originale sia ai decoratori. Ogni decoratore conserva un riferimento a un componente e aggiunge il proprio comportamento prima o dopo aver delegato all'oggetto racchiuso:

#include <iostream>
#include <memory>

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

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

// Decoratore di base
class CoffeeDecorator : public Coffee {
protected:
    std::unique_ptr<Coffee> coffee;
public:
    CoffeeDecorator(std::unique_ptr<Coffee> c) : coffee(std::move(c)) {}
};

// Decoratori concreti
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; }
};

I decoratori possono essere sovrapposti: ciascuno avvolge il precedente, aggiungendo funzionalità strato dopo strato:

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

Usa Decorator quando devi aggiungere responsabilità agli oggetti dinamicamente senza influire sugli altri oggetti, o quando estendere le funzionalità tramite sottoclassi porterebbe a un’esplosione di classi.

challenge icon

Sfida

Facile

Costruiamo un Sistema di ordinazione delle pizze usando il pattern Decorator. Creerai un sistema flessibile in cui i clienti possono partire da una pizza base e aggiungere vari condimenti: ogni condimento avvolge l’ordine precedente e aggiunge il proprio costo e la propria descrizione. È un caso d’uso perfetto per i decorator: i condimenti possono essere combinati in qualsiasi modo senza creare una proliferazione di sottoclassi.

Organizzerai il codice in tre file:

  • Pizza.h: definisci l’interfaccia del componente e la classe della pizza base.

    Crea una classe astratta Pizza con i metodi virtuali puri getDescription() (restituisce una stringa) e getCost() (restituisce un double), oltre a un distruttore virtuale.

    Poi crea una classe concreta PlainPizza che rappresenta la pizza base. Dovrebbe restituire "Pizza" come descrizione e avere un costo base di 8.0.

  • ToppingDecorator.h: crea la classe base del decorator e i decorator concreti dei condimenti.

    Crea una classe ToppingDecorator che eredita da Pizza e contiene uno std::unique_ptr<Pizza> al componente avvolto. Questa classe funge da base per tutti i decorator dei condimenti.

    Implementa tre decorator concreti dei condimenti:

    • CheeseTopping: aggiunge ", Cheese" alla descrizione e 1.5 al costo
    • PepperoniTopping: aggiunge ", Pepperoni" alla descrizione e 2.0 al costo
    • MushroomTopping: aggiunge ", Mushrooms" alla descrizione e 1.0 al costo

    Ogni decorator dovrebbe delegare alla pizza avvolta e poi aggiungere il proprio contributo.

  • main.cpp: crea un ordine di pizza personalizzato in base all’input dell’utente.

    Leggi tre input (ciascuno sarà yes oppure no):

    1. Aggiungere il formaggio?
    2. Aggiungere i pepperoni?
    3. Aggiungere i funghi?

    Parti da una PlainPizza, poi avvolgila con i decorator dei condimenti appropriati in base agli input (nell’ordine: formaggio, pepperoni, funghi). Infine, stampa i dettagli dell’ordine in questo formato:

    Order: [description]
    Total: $[cost]

Per esempio, con gli input yes, yes, no:

Order: Pizza, Cheese, Pepperoni
Total: $11.5

Con gli input yes, no, yes:

Order: Pizza, Cheese, Mushrooms
Total: $10.5

Con gli input no, no, no:

Order: Pizza
Total: $8

Con gli input yes, yes, yes:

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

Nota come ogni decorator avvolga il precedente, costruendo descrizione e costo strato dopo strato. Puoi aggiungere facilmente nuovi condimenti creando nuove classi decorator senza modificare il codice esistente della pizza o dei condimenti: questa è la bellezza del pattern Decorator.

Provalo tu

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

using namespace std;

int main() {
    // Leggi tre input (sì o no)
    string addCheese, addPepperoni, addMushrooms;
    cin >> addCheese;
    cin >> addPepperoni;
    cin >> addMushrooms;
    
    // TODO: Inizia con una PlainPizza usando std::unique_ptr
    
    // TODO: Se addCheese è "yes", avvolgi la pizza con CheeseTopping
    
    // TODO: Se addPepperoni è "yes", avvolgi la pizza con PepperoniTopping
    
    // TODO: Se addMushrooms è "yes", avvolgi la pizza con MushroomTopping
    
    // TODO: Stampa l'ordine nel formato:
    // Order: [description]
    // Total: $[cost]
    
    return 0;
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore C++ online