Menu
Coddy logo textTech

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.

challenge icon

Défi

Facile

Construisons 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 Pizza avec les méthodes virtuelles pures getDescription() (qui renvoie une chaîne) et getCost() (qui renvoie un nombre réel de type double), ainsi qu'un destructeur virtuel.

    Crée ensuite une classe concrète PlainPizza qui représente la pizza de base. Elle doit renvoyer "Pizza" comme description et avoir un coût de base de 8.0.

  • ToppingDecorator.h : construire la classe de base du décorateur et les décorateurs concrets de garnitures.

    Crée une classe ToppingDecorator qui hérite de Pizza et contient un std::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 et 1.5 au coût
    • PepperoniTopping : ajoute ", Pepperoni" à la description et 2.0 au coût
    • MushroomTopping : ajoute ", Mushrooms" à la description et 1.0 au 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 yes ou no) :

    1. Ajouter du fromage ?
    2. Ajouter du pepperoni ?
    3. 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.5

Avec les entrées yes, no, yes :

Order: Pizza, Cheese, Mushrooms
Total: $10.5

Avec les entrées no, no, no :

Order: Pizza
Total: $8

Avec les entrées yes, yes, yes :

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

Remarque 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;
}
quiz iconTestez-vous

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

Entraînez-vous par vous-même : Compilateur C++ en ligne