Menu
Coddy logo textTech

Patron Décorateur

Fait partie de la section Programmation Orientée Objet du Journey C# de Coddy — leçon 58 sur 70.

Le patron Décorateur est un patron de conception structurel qui permet d'attacher de nouveaux comportements à des objets en les plaçant dans des objets spéciaux appelés décorateurs. Contrairement à l'héritage, qui ajoute du comportement au moment de la compilation, les décorateurs ajoutent des fonctionnalités de manière dynamique au moment de l'exécution sans modifier la classe d'origine.

Le patron fonctionne en faisant en sorte que les décorateurs implémentent la même interface que l'objet qu'ils enveloppent. Chaque décorateur détient une référence à l'objet enveloppé et lui délègue les appels, en ajoutant son propre comportement avant ou après :

public interface ICoffee
{
    string GetDescription();
    decimal GetCost();
}

public class SimpleCoffee : ICoffee
{
    public string GetDescription() => "Coffee";
    public decimal GetCost() => 2.00m;
}

public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;
    
    public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
    
    public virtual string GetDescription() => _coffee.GetDescription();
    public virtual decimal GetCost() => _coffee.GetCost();
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) { }
    
    public override string GetDescription() => _coffee.GetDescription() + ", Milk";
    public override decimal GetCost() => _coffee.GetCost() + 0.50m;
}

La puissance des décorateurs réside dans leur capacité à se superposer. Vous pouvez envelopper un objet décoré avec un autre décorateur :

ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new MilkDecorator(coffee);  // Double lait !

Console.WriteLine(coffee.GetDescription());  // Café, Lait, Lait
Console.WriteLine(coffee.GetCost());         // 3.00

Le patron de conception Décorateur est idéal lorsque vous devez ajouter des responsabilités à des objets sans créer une explosion de sous-classes. Chaque décorateur se concentre sur une seule préoccupation, et vous les combinez selon vos besoins pour construire le comportement exact que vous souhaitez.

challenge icon

Défi

Facile

Construisons un système de commande de pizza en utilisant le motif de conception Decorator (Décorateur). Vous allez créer une pizza de base, puis y ajouter dynamiquement des garnitures, chaque garniture enveloppant la couche précédente et s'ajoutant à la fois à la description et au prix.

Vous organiserez votre code sur trois fichiers :

  • Pizza.cs : Définissez une interface IPizza dans l'espace de noms PizzaShop avec deux méthodes : GetDescription() retournant une chaîne de caractères et GetPrice() retournant un décimal. Créez ensuite une classe PlainPizza qui implémente cette interface - elle doit retourner "Pizza" comme description et 8.00m comme prix de base.
  • ToppingDecorators.cs : Créez une classe abstraite ToppingDecorator qui implémente IPizza et détient une référence à la pizza enveloppée (passée via le constructeur). Ses méthodes virtuelles doivent déléguer à la pizza enveloppée. Créez ensuite trois décorateurs concrets :
    • CheeseTopping - ajoute ", Cheese" à la description et ajoute 1.50m au prix
    • PepperoniTopping - ajoute ", Pepperoni" à la description et ajoute 2.00m au prix
    • MushroomTopping - ajoute ", Mushrooms" à la description et ajoute 1.25m au prix
  • Program.cs : Rassemblez le tout en commençant par une pizza nature et en l'enveloppant de garnitures en fonction de l'entrée. Chaque décorateur de garniture enveloppe la pizza précédente, construisant la commande finale couche par couche.

Vous recevrez une entrée :

  • Une liste de garnitures séparées par des virgules à ajouter (par exemple, cheese,pepperoni,cheese)

Commencez par une PlainPizza, puis enveloppez-la avec le décorateur approprié pour chaque garniture dans l'ordre où elles apparaissent. Les garnitures valides sont cheese, pepperoni, et mushrooms. Après avoir appliqué toutes les garnitures, affichez la description sur une ligne et le prix total sur la ligne suivante.

Par exemple, si l'entrée est cheese,pepperoni,mushrooms, la sortie devrait être :

Pizza, Cheese, Pepperoni, Mushrooms
12.75

Remarquez comment vous pouvez ajouter la même garniture plusieurs fois ! Si l'entrée est cheese,cheese, vous obtiendriez :

Pizza, Cheese, Cheese
11

Cela démontre la flexibilité du motif décorateur - chaque décorateur enveloppe n'importe quelle pizza qu'il reçoit, qu'il s'agisse d'une pizza nature ou d'une pizza déjà décorée, vous permettant d'empiler les comportements de manière dynamique !

Aide-mémoire

Le pattern Décorateur est un patron de structure qui attache de nouveaux comportements à des objets en les enveloppant dans des objets décorateurs spéciaux. Il ajoute des fonctionnalités de manière dynamique au moment de l'exécution sans modifier la classe d'origine.

Les décorateurs implémentent la même interface que l'objet qu'ils enveloppent, détiennent une référence à l'objet enveloppé et lui délèguent les appels tout en ajoutant leur propre comportement :

public interface ICoffee
{
    string GetDescription();
    decimal GetCost();
}

public class SimpleCoffee : ICoffee
{
    public string GetDescription() => "Coffee";
    public decimal GetCost() => 2.00m;
}

public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;
    
    public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
    
    public virtual string GetDescription() => _coffee.GetDescription();
    public virtual decimal GetCost() => _coffee.GetCost();
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) { }
    
    public override string GetDescription() => _coffee.GetDescription() + ", Milk";
    public override decimal GetCost() => _coffee.GetCost() + 0.50m;
}

Les décorateurs peuvent être empilés en enveloppant un objet décoré avec un autre décorateur :

ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new MilkDecorator(coffee);  // Double lait !

Console.WriteLine(coffee.GetDescription());  // Coffee, Milk, Milk
Console.WriteLine(coffee.GetCost());         // 3.00

Utilisez le pattern Décorateur lorsque vous devez ajouter des responsabilités à des objets sans créer de nombreuses sous-classes. Chaque décorateur se concentre sur une seule préoccupation et peut être combiné selon les besoins.

Essayez vous-même

using System;
using PizzaShop;

class Program
{
    public static void Main(string[] args)
    {
        // Lire la liste des garnitures séparées par des virgules
        string input = Console.ReadLine();
        string[] toppings = input.Split(',');
        
        // TODO: Commencer avec une PlainPizza
        
        // TODO: Parcourir chaque garniture et envelopper la pizza
        // avec le décorateur approprié basé sur le nom de la garniture :
        // - "cheese" -> CheeseTopping
        // - "pepperoni" -> PepperoniTopping
        // - "mushrooms" -> MushroomTopping
        
        // TODO: Afficher la description finale et le prix
    }
}
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