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.
Sfida
FacileCostruiamo 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
Pizzacon i metodi virtuali purigetDescription()(restituisce una stringa) egetCost()(restituisce un double), oltre a un distruttore virtuale.Poi crea una classe concreta
PlainPizzache rappresenta la pizza base. Dovrebbe restituire"Pizza"come descrizione e avere un costo base di8.0.ToppingDecorator.h: crea la classe base del decorator e i decorator concreti dei condimenti.Crea una classe
ToppingDecoratorche eredita daPizzae contiene unostd::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 e1.5al costoPepperoniTopping: aggiunge", Pepperoni"alla descrizione e2.0al costoMushroomTopping: aggiunge", Mushrooms"alla descrizione e1.0al 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à
yesoppureno):- Aggiungere il formaggio?
- Aggiungere i pepperoni?
- 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.5Con gli input yes, no, yes:
Order: Pizza, Cheese, Mushrooms
Total: $10.5Con gli input no, no, no:
Order: Pizza
Total: $8Con gli input yes, yes, yes:
Order: Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.5Nota 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;
}
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
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore genericoEsercitati da solo: Compilatore C++ online