Le design pattern Decorator (Décorateur)

Définition :

Le pattern Décorator est un design pattern (patron de conception) de structure qui fait partie des design pattern du GoF (Gang of Four).
Decorator nous permet d’ajouter des fonctionnalités nouvelles à une classe de façon dynamique sans impacter les classes qui l’utilisent ou en héritent.

Pour mieux comprendre comment il fonctionne j’ai créé un programme simple le mettant en oeuvre :

Implémentation :

cdDecorator

 

On va prendre l’exemple d’un vendeur de HotDog. Le décorateur sera l’ajout de supplément à nos différents HotDogs.

HotDogsComponent: définit la classe abstraite du composant de base c’est-à-dire notre matière première pour le HotDog

HotDogMeuh, HotDogBase, HotDogBzz: héritent de HotDogsComponent et sont des composants concrets autrement dit nos différents HotDog proposés à la vente.

Decorator: est une classe abstraite qui hérite de HotDogsComponent qui permet de décorer le composant de base, qu’on assimile à l’ajout d’un supplément à nos HotDog.

MoutardeCidreMielDecorator, MoutardeGingembreDecorator : héritent de Décorator permettent d’agrémenter notre élément de base sans modifier notre classe de base. En l’occurence le supplément ici sera de la sauce avec un coût supplémentaire s’ajoutant au prix du HotDog choisi.

Le code

HotDogsComponent :

Ci-après, notre classe abstraite qui représente le composant de base : un HotDog avec un nom et un prix.

public abstract class HotDogsComponent

{
    protected string h_nom;
    public virtual string GetName()
    {
       return h_nom;
    }

    protected double h_prix;
    public virtual double GetPrice()
    {
       return h_prix;
    }
}

HotDogBase, HotDogMeuh , HotDogBzz :

Nous avons 3 classes concrètes qui héritent du composant de base, auxquelles on affecte un prix et un nom :

class HotDogBase : HotDogsComponent
{
   public HotDogBase()
   {
       h_nom = "Hot Dog Base";
       h_prix = 5.0;
   }
} 

class HotDogBzz : HotDogsComponent
{
    public HotDogBzz()
    {
       h_nom = "Hot Dog Bzz";
       h_prix =7.5;
    }
}

class HotDogMeuh : HotDogsComponent
{
   public HotDogMeuh()
   {
       h_nom = Hot Dog Meuh";
       h_prix = 6.5;
   }
}

Decorator :

Notre Décorator hérite de notre composant de base auquel on fixe des règles, à savoir l’ajout d’un nom de sauce à la suite du nom du hotdog et le supplément ajouté au prix du hotdog initial :

public abstract class Decorator : HotDogsComponent
{
   HotDogsComponent h_BaseComponent = null;

   protected string h_Nom = "Decorateur indéfini";
   protected double h_prix = 0.0;

   protected Decorator(HotDogsComponent hotdog)
   {
      h_BaseComponent = hotdog;
   }

#region membres

   public override string GetName()
   {
      return string.Format("{0}, {1}", h_BaseComponent.GetName(), h_Nom);
   }

   public override double GetPrice()
   {
      return h_prix + h_BaseComponent.GetPrice();
   }
#endregion
}

 MoutardeCidreMielDecorator, MoutardeGingembreDecorator :

Nous avons deux classes concrètes héritant de Decorator, auxquelles on affecte un prix et un nom :

class MoutardeCidreMielDecorator : Decorator
{
   public MoutardeCidreMielDecorator(HotDogsComponent hotdogmoutarde)
: base(hotdogmoutarde)
   {
      this.h_Nom = "Moutarde Cidre Miel";
      this.h_prix = 5.0;
   }
}

class MoutardeGingembreDecorator : Decorator
{
    public MoutardeGingembreDecorator(HotDogsComponent hotDog)
: base(hotDog)
   {
      this.h_Nom = "Moutarde Gingembre";
      this.h_prix = 4.0;
   }
}

Program.cs :

Enfin, nous créons une application console qui nous affiche les différents choix de HotDog avec ou sans supplément :

class Program
{
   static void Main(string[] args)
{
   HotDogBase hBase = new HotDogBase();
   PrintHotDogDetails(hBase);

   HotDogMeuh hotDogMeuh = new HotDogMeuh();
   PrintHotDogDetails(hotDogMeuh);

   HotDogBzz hotDogBzz = new HotDogBzz();
   PrintHotDogDetails(hotDogBzz);

   MoutardeGingembreDecorator hotDogMoutardeGingembre = new MoutardeGingembreDecorator(hotDogMeuh);
   PrintHotDogDetails(hotDogMoutardeGingembre);

   MoutardeCidreMielDecorator hotCidreMielDecorator = new MoutardeCidreMielDecorator(hotDogBzz);
   PrintHotDogDetails(hotCidreMielDecorator);

}
   private static void PrintHotDogDetails(HotDogsComponent hotdog)
   {
      Console.WriteLine();
      Console.WriteLine("Sandwich: {0}, Prix: {1}", hotdog.GetName(), hotdog.GetPrice());
   }
}

Résultat :

HotDog

Conclusion :

Ce pattern est très intéressant à utiliser lors de la conception de classes qui risquent d’évoluer fortement par ajout et modification dynamiques de fonctionnalités. C’est un patron de conception très souple.

Dans notre cas l’intérêt premier est de pouvoir étendre les fonctionnalités offertes par la classe HotDogComponent sans la modifier et ainsi éviter de modifier le reste du système et en respectant le principe « Open Close« .

Cependant il faut bien réfléchir aux points sensibles de l’application dès la phase d’analyse pour éviter une conception trop complexe.

Un commentaire. Laisser un commentaire

Quel est l’Intérêt de ce design comparé a une approche plus classique qui consisterait a dire qu’un hot dog se compose d’un hot dog de base + une liste d’ingrédients supplémentaires ?

Je trouve ça ultra compliqué et peu pratique a utiliser. Par exemple, ajouter ou supprimer des ingrédients de manière interactive devient plus dur, changer le hot dog de base aussi….

Bref, a mon avis c’est un pattern qu’il faut utiliser avec précautions.

Répondre

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *