Description
Le design pattern Decorator résout le problème d'extension des classes, où chaque objet peut être étendu sans influencer les fonctionnalités des autres objets de la même classe. Ce design pattern se rapproche du design Chain of Responsibility, mais la différence vient du fait que toutes les demandes sont reçues par toutes les classes par le biais d'un élément central.
En d'autres termes, ce design pattern permet d'ajouter des fonctionnalités à un objet sans influencer les autres, apportant dès lors plus de souplesse à l'héritage. Ce modèle crée une bonne alternative à l'utilisation de sous-classe, qui ne permettent pas des ajustements pendant l'exécution, là où ce design pattern le permet.
Mise en situation
📚 Vous développez un site permettant de vendre en ligne des livres. Le concept de ce magasin est de vendre des livres à des prix fixes, ainsi sur certains livres vous êtes moins cher que la concurrence ! Dans votre e-commerce, vous vendez plusieurs catégories de livres :
| Catégorie | Prix |
|---|---|
| Livres d'informatique | 15$ |
| Mangas | 10$ |
| Romans d'aventures | 20$ |
Le prix minimal des livres est fixé à 5$ (histoire de business plan).
Votre cahier des charges pour le lancement de votre plateforme est établi, mais vous savez qu'à l'avenir il y aura d'autres catégories. Une solution sera d'ajouter d'autres sous-classes qui implémenteront une interface commune aux autres livres. Mais le souci est que si celles-ci deviennent nombreuses, on aura trop de sous-classes qui implémenteront leur logique là où nous aurions des fonctionnalités communes pour chaque livre.
C'est là que le pattern Decorator vient à la rescousse :
- Nous aurons une interface commune pour tous les livres
- Cette interface sera implémentée par le décorateur qui s'occupera de réunir les informations communes aux objets.
- Chaque catégorie étendra alors le décorateur et surchargera les données qu'elle souhaite modifier.
Avantages et inconvénients
| Avantages | Inconvénients |
|---|---|
| ✔️Flexibilité par rapport à l'héritage statique, car il offre plus de possibilités d'ajout de fonctionnalité. Ce qui permet avec le décorateur d'ajouter/supprimer des fonctionnalités au cours de l'exécution grâce au décorateur. | ❌Complexité par l'ajout de nombreuses classes qui peuvent rendre le système plus complexe. Cet effet mène à des soucis de debugging, étant donné que le nombre de classes implémentées peut faire perdre du temps à la recherche de solutions. |
| ✔️Lisibilité du code, dans l'héritage statique on a une hiérarchie de classe où la classe la plus haute est chargée. Le décorateur permet de ne pas avoir une classe concrète qui définit toutes les méthodes, mais plusieurs classes qui utilisent des méthodes qui lui sont propres. | ❌Pas adapté aux débutants car lorsque le système devient complexe il y a beaucoup de code généré. Ce qui peut le rendre difficile de compréhension pour un débutant. |
| ✔️Performance: traditionnellement on aurait une classe qui implémente toutes les méthodes, alors que le pattern prône l'utilisation de fonctionnalité spécifique pour un cas d'usage. |
🔹 Quand l'utiliser ?
- Vous l'utilisez souvent inconsciemment lorsque vous développez des interfaces d'utilisation.
- Vous l'utilisez lorsque vous créez une gestion d'entrée et de sortie de données.
- Vous implémentez un héritage qui peut avoir énormément d'enfants et de fonctionnalités différentes.
Diagramme UML

Dans le cas de cet exemple, nous développons un e-commerce vendant des livres en ligne à prix unique ! Nous avons plusieurs catégories de livres qui pourraient évoluer dans le futur.
Face à cette situation, nous remarquons qu'un pattern decorator est une très bonne solution.
Mais pourquoi ? Imaginons qu’une nouvelle catégorie voit le jour, dans le cas d'un héritage statique on aura une classe implémentée qui hériterait d'une classe et ainsi de suite. Dans le cas du décorateur chacune des classes va ajouter sa logique !
Implémentation
Commençons par créer la base de notre design pattern : l'interface BookCategory.
package com.design.decorator.category;
public interface BookCategory {
double cost();
}
Notre interface de base n'est pas très compliquée pour l'instant, elle demande l'implémentation d'une classe Cost qui permettra de définir le coût de notre livre.
Bien, maintenant que nous avons terminé notre interface, ajoutons un livre de base, qui dans notre commerce désigne le prix minimum d'un livre et n'a pas de réelle catégorie attachée.
package com.design.decorator.category.impl;
import com.design.decorator.category.BookCategory;
public class BasicBook implements BookCategory {
public BasicBook() {
System.out.println("Buy a basic book");
}
@Override
public double cost() {
return 5.00;
}
}
Parfait ! Maintenant rentrons dans le vif du sujet du décorateur. Pour cela, nous avons besoin que notre décorateur implémente notre interface BookCategory. Aussi, il nous faut un objet de base qui sera notre BasicBook qui nous donnera la base de nos catégories.
package com.design.decorator.decorator;
import com.design.decorator.category.BookCategory;
public class BookDecorator implements BookCategory {
private BookCategorybookCategory;
public BookDecorator(BookCategory bookCategory) {
this.bookCategory = bookCategory;
}
@Override
public double cost) {
return this.bookCategory.cost();
}
}
Notre décorateur est implémenté, maintenant passons aux classes qui vont étendre notre décorateur et fixer leurs prix.
package com.design.decorator.category.impl;
import com.design.decorator.category.BookCategory;
import com.design.decorator.decorator.BookDecorator;
public class AdventureBook extends BookDecorator {
public AdventureBook(BookCategory bookCategory) {
super(bookCategory);
}
@Override
public double cost() {
System.out.println("Buy adventure book");
return 15.00 + super.cost();
}
package com.design.decorator.category.impl;
import com.design.decorator.category.BookCategory;
import com.design.decorator.decorator.BookDecorator;
public class MangaBook extends BookDecorator {
public MangaBook(BookCategory bookCategory) {
super(bookCategory);
}
@Override
public double cost() {
System.out.println("Buy manga book");
return 5.00 + super.cost();
}
}
package com.design.decorator.category.impl;
import com.design.decorator.category.BookCategory;
import com.design.decorator.decorator.BookDecorator;
public class ItBook extends BookDecorator {
public ItBook(BookCategory bookCategory) {
super(bookCategory);
}
@Override
public double cost() {
System.out.println("Buy it book");
return 10.00 + super.cost();
}
}
Bon, nous avons tout ce dont nous avons besoin pour tester notre code dans notre classe main ! Il nous reste à implémenter tout cela :
package com.design.decorator;
import com.design.decorator.category.BookCategory;
import com.design.decorator.category.impl.AdventureBook;
import com.design.decorator.category.impl.BasicBook;
import com.design.decorator.category.impl.ItBook;
import com.design.decorator.category.impl.MangaBook;
public class Main {
public static void main(String[] args) {
BookCategory basicBook = new BasicBook();
System.out.println("Basic book cost " + basicBook.cost() + "$");
//Manga
BookCategory manga = new MangaBook(basicBook); //wrapping
System.out.println("Manga book cost " + manga.cost() + "$");
//It
BookCategory it = new ItBook(basicBook); //wrapping
System.out.println("It book cost " + it.cost() + "$");
//Adventure
BookCategory adventure = new AdventureBook(basicBook); //wrapping
System.out.println("Adventure book cost " + adventure.cost() + "$");
}
}
Resultat :
Buy a basic book
Basic book cost 5.0$
Buy manga book
Manga book cost 10.0$
Buy it book
It book cost 15.0$
Buy adventure book
Adventure book cost 20.0$
Ici, nous avons donc fini notre implémentation. Ce que nous pouvons remarquer :
- Nous avons créé une classe concrète de base et l'on pourrait en avoir d'autres à l'avenir.
- Notre logique est découpée par catégorie de livre et non pas basée sur une classe concrète.
- Notre décorateur est la classe de référence pour des composants de la même famille.
- Notre interface est à la base de notre classe et permet d'ajouter la flexibilité en n'implémentant pas directement un objet.





