Pattern Décorateur
Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy. Leçon 98 sur 104.
Le pattern Decorator vous permet d’ajouter dynamiquement de nouveaux comportements aux objets en les enveloppant dans des objets spéciaux appelés décorateurs. Contrairement à l’héritage, qui ajoute des comportements au moment de la compilation, les décorateurs vous permettent d’étendre les fonctionnalités à l’exécution sans modifier la classe d’origine.
Le modèle fonctionne en faisant en sorte que l’objet d’origine et les décorateurs implémentent la même interface. Chaque décorateur conserve une référence vers un composant et ajoute son propre comportement avant ou après avoir délégué l’exécution à l’objet enveloppé :
#include <iostream>
#include <memory>
// Interface du composant
class Coffee {
public:
virtual std::string getDescription() const = 0;
virtual double getCost() const = 0;
virtual ~Coffee() = default;
};
// Composant concret
class SimpleCoffee : public Coffee {
public:
std::string getDescription() const override { return "Coffee"; }
double getCost() const override { return 2.0; }
};
// Décorateur de base
class CoffeeDecorator : public Coffee {
protected:
std::unique_ptr<Coffee> coffee;
public:
CoffeeDecorator(std::unique_ptr<Coffee> c) : coffee(std::move(c)) {}
};
// Décorateurs concrets
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; }
};Les décorateurs peuvent être empilés : chacun enveloppe le précédent, ajoutant des fonctionnalités couche par couche :
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();
// Sortie : Coffee, Milk, Sugar: $2.7
}Utilisez Decorator lorsque vous devez ajouter dynamiquement des responsabilités aux objets sans affecter les autres objets, ou lorsque l’extension des fonctionnalités par sous-classement entraînerait une prolifération de classes.
Défi
FacileConstruisons un système de commande de pizzas en utilisant le pattern Decorator. Tu vas créer un système flexible dans lequel les clients peuvent commencer avec une pizza de base et ajouter différentes garnitures : chaque garniture enveloppe la commande précédente et lui ajoute son propre coût et sa propre description. C'est un cas d'utilisation parfait pour les décorateurs : les garnitures peuvent être empilées dans n'importe quelle combinaison sans créer une explosion de sous-classes.
Tu organiseras ton code dans trois fichiers :
Pizza.h: définir l'interface du composant et la classe de pizza de base.Crée une classe abstraite
Pizzaavec les méthodes virtuelles puresgetDescription()(qui renvoie une chaîne) etgetCost()(qui renvoie un nombre réel de type double), ainsi qu'un destructeur virtuel.Crée ensuite une classe concrète
PlainPizzaqui représente la pizza de base. Elle doit renvoyer"Pizza"comme description et avoir un coût de base de8.0.ToppingDecorator.h: construire la classe de base du décorateur et les décorateurs concrets de garnitures.Crée une classe
ToppingDecoratorqui hérite dePizzaet contient unstd::unique_ptr<Pizza>vers le composant enveloppé. Elle sert de base à tous les décorateurs de garnitures.Implémente trois décorateurs concrets de garnitures :
CheeseTopping: ajoute", Cheese"à la description et1.5au coûtPepperoniTopping: ajoute", Pepperoni"à la description et2.0au coûtMushroomTopping: ajoute", Mushrooms"à la description et1.0au coût
Chaque décorateur doit déléguer à la pizza enveloppée, puis ajouter sa propre contribution.
main.cpp: créer une commande de pizza personnalisée à partir des entrées de l'utilisateur.Lis trois entrées (chacune sera
yesouno) :- Ajouter du fromage ?
- Ajouter du pepperoni ?
- Ajouter des champignons ?
Commence par une
PlainPizza, puis enveloppe-la avec les décorateurs de garnitures appropriés en fonction des entrées (dans l'ordre : fromage, pepperoni, champignons). Enfin, affiche les détails de la commande dans ce format :Order: [description] Total: $[cost]
Par exemple, avec les entrées yes, yes, no :
Order: Pizza, Cheese, Pepperoni
Total: $11.5Avec les entrées yes, no, yes :
Order: Pizza, Cheese, Mushrooms
Total: $10.5Avec les entrées no, no, no :
Order: Pizza
Total: $8Avec les entrées yes, yes, yes :
Order: Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.5Remarque comment chaque décorateur enveloppe le précédent, en construisant progressivement la description et le coût, couche après couche. Tu peux facilement ajouter de nouvelles garnitures en créant de nouvelles classes de décorateurs sans modifier le code existant de la pizza ou des garnitures : c'est toute la beauté du pattern Decorator.
Essayez vous-même
#include <iostream>
#include <string>
#include <memory>
#include "Pizza.h"
#include "ToppingDecorator.h"
using namespace std;
int main() {
// Lire trois entrées (yes ou no)
string addCheese, addPepperoni, addMushrooms;
cin >> addCheese;
cin >> addPepperoni;
cin >> addMushrooms;
// TODO: Commencer avec une PlainPizza en utilisant std::unique_ptr
// TODO: Si addCheese est "yes", envelopper la pizza avec CheeseTopping
// TODO: Si addPepperoni est "yes", envelopper la pizza avec PepperoniTopping
// TODO: Si addMushrooms est "yes", envelopper la pizza avec MushroomTopping
// TODO: Afficher la commande au format :
// Order: [description]
// Total: $[cost]
return 0;
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO
Fichiers externesGénération et compilation en C++Fichiers d’en-tête et fichiers sourceEspaces de noms et portéeIntroduction à la POO en C++Classes et objetsLe pointeur « this »Méthodes (fonctions membres)Attributs (membres de données)Bases des constructeurs et destructeursRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d’instance et statiquesAccesseurs et mutateursFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d’accès de l’héritageOrdre d’appel des constructeurs et destructeursRedéfinition des méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif : hiérarchie des employés10Vue d’ensemble de la STL
Vue d’ensemble et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - fréquence des mots13Modèles de conception, partie 1
Introduction aux modèles de conceptionModèle SingletonFabrique et fabrique abstraiteModèle BuilderModèle ObserverModèle Strategy2Gestion de la mémoire
Mémoire de pile ou de tasPointeurs et référencesMémoire dynamique (new/delete)Pointeurs intelligents en C++RAII en C++Récapitulatif - Gestionnaire de tableaux dynamiques5Encapsulation
Spécificateurs d’accès en C++Spécificateurs d’accès en profondeurMasquage de l’informationStruct vs classeClasses imbriquées et internesRécapitulatif - Système de gestion des dossiers étudiants8Polymorphisme
Polymorphisme à la compilation vs à l’exécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d’interfaces en C++Conversion dynamique et RTTIRécapitulatif - Calculateur de formes11Concepts avancés de la POO
Composition ou héritageMixins via CRTPIdiom PimplEffacement de typeClasses enum et typage fortGestion des exceptions en POOHiérarchies d’exceptions personnalisées14Design Patterns – Partie 2
Pattern CommandePattern AdaptateurPattern DécorateurPattern Méthode ModèlePattern ÉtatPattern CompositeRAII comme Pattern3Constructeurs et destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d’initialisation des constructeursConstructeurs déléguésApprofondissement des destructeursRègle des trois / cinq / zéroRécapitulatif - Classe String6Surcharge des opérateurs
Introduction à la surcharge des opérateursSurcharge des opérateurs arithmétiquesSurcharge des opérateurs de comparaisonOpérateurs de fluxSurcharge de l’opérateur d’affectationSurcharge des opérateurs [] et ()Opérateurs de conversion de typeRécapitulatif – Classe Matrix9Templates
Templates de fonctionsTemplates de classesSpécialisation des templatesTemplates variadiquesBases de SFINAE et des traits de typesRécapitulatif - Conteneur génériqueEntraînez-vous par vous-même : Compilateur C++ en ligne