Design pattern Decorator (décorateur)
Très souvent, lorsque l’on souhaite ajouter des fonctionnalités à une classe on pense : héritage – et c’est un bon réflexe.
Mais - très souvent également- la solution n’est pas forcément adapté : on peut souhaiter ajouter plusieurs fonctionnalités à la même classe de manière dynamique (runtime) et non pas à l’écriture du programme (ajout de fonctionnalité statique) en choisissant les nouvelles fonctionnalités pour chaque instance et non pas pour la classe. Dans ces cas, on doit faire appel au pattern Decorator (pattern du GoF de type structure).
Prenons un exemple simple pour illustrer ce pattern trop peu utilisé dans notre écosystème.
Prenons, par exemple, la modélisation de glaces :
· il en existe plein de types :à l’eau, italienne, avec des boules,…
· et tous ces types de glace on en commun des gouts : chocolat, vanille, citron, etc…
On souhaite donc pouvoir avoir un modèle permettant d’avoir une glace Italienne au chocolat et à la vanille. On va donc pouvoir utiliser l’héritage avec par exemple : la classe Italienne (qui hérite de glace) et qui a comme propriété une liste de gouts
Sauf que si on ajoute des options tels que des amandes grillés dessus ou autre chose, on complexifie le modèle et surtout on risque de ne plus respecter le principe SOLID « open close principle ».
Voyons comme le pattern Decorator peut nous aider de manière élégante :
Dans ce pattern, on va identifier deux parties de l’arbre d’éritage:
1. la partie classique ou l’on va retrouver nos classes Glace (abstraite) GlaceItalienne et GlaceALeau.
2. La partie liée aux ajouts de fonctionnalités avec
a. une autre classe abstraite GlaceDecorator qui servira de base pour tous les décorator (ie fonctionnalités)
b. les décorateurs concrets : GoutVanille & GoutChocolat qui en héritent.

Pour le code, on va juste compléter la description de la glace :
public abstract class Glace
{
public virtual string Description
{
get
{
return "Glace virtuelle";
}
}
}
public class GlaceItalienne : Glace
{
public override string Description
{
get
{
return "glace italienne";
}
}
}
public class GlaceALeau : Glace
{
public override string Description
{
get
{
return "glace a l'eau";
}
}
}
public abstract class GlaceDecorator : Glace
{
protected Glace _glace;
public override string Description
{
get
{
return _glace.Description;
}
}
}
public class GoutChocolat : GlaceDecorator
{
public GoutChocolat(Glace glace)
{
_glace = glace;
}
public override string Description
{
get
{
return _glace.Description + " avec du chocolat ";
}
}
}
public class GoutVanille : GlaceDecorator
{
public GoutVanille(Glace glace)
{
_glace = glace;
}
public override string Description
{
get
{
return _glace.Description + " avec de la vanille ";
}
}
}
Et donc, à l’utilisation j’ai juste a instancier une glace italienne et ajouter mes options vanille et chocolat par exemple :
static void Main(string[] args)
{
Glace maglace = new GlaceItalienne();
maglace = new GoutChocolat(maglace);
maglace = new GoutVanille(maglace);
Console.WriteLine(maglace.Description);
Console.ReadKey();
}
J’ai le résultat suivant à la console :

De cette manière, avec un arbre d’héritage peu complexe, on a la possibilité d’avoir des compositions dynamiques très complexes tout en respectant le principe « open close ».
Un des avantage de ce pattern est l’aspect dynamique et optionnel des fonctionnalités que l’on ajoute et, mine de rien, si on le met en place suffisamment tôt lorsque l’on développe la solution reste simple et élégante.
Par contre, contrairement à l’héritage nous n’avons pas accès aux propriétés et méthodes privés de l’objet que l’on décore.
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :