Langage de programmation:
Design pattern: 

Implémentation du pattern Décorateur en Java sur le thème de la vente de desserts.

Description du problème

Afin de mettre en pratique le pattern Décorateur, nous allons concevoir une application qui permet de gérer la vente de desserts. Celle-ci doit permettre d’afficher dans la console le nom complet du dessert choisi et son prix. Les clients ont le choix entre deux desserts : crêpe ou gaufre. Sur chaque dessert ils peuvent ajouter un nombre quelconque d’ingrédients afin de faire leurs propres assortiments. Pour simplifier notre exemple, nous choisirons uniquement deux ingrédients (le chocolat et la chantilly) mais il faut garder à l’esprit que l’ajout de nouveaux ingrédients doit être simplifié. Le système de tarification est simple. Une crêpe (nature) coûte 1.50€ et une gaufre 1.80€. L’ajout de chocolat est facturé 0.20€ et de chantilly 0.50€.

Voyons comment concevoir cette application. Une première idée est de mettre en place une classe abstraite Dessert ayant deux attributs (libelle et prix) et les accesseurs en lecture/écriture correspondants. Puis, créer pour chaque combinaison de desserts et d’ingrédients une classe (CrepeChocolat, CrepeChantilly, GaufreChocolat, GaufreChantilly). Bien sûr cette solution n’est pas évolutive. Si l’on souhaite modifier le prix de l’ingrédient Chocolat on doit le modifier dans deux classes. De plus, si on ajoute une dizaine d’ingrédients nous allons obtenir une centaine de classes.

Une deuxième solution consiste à garder la classe Dessert en la modifiant légèrement. On peut rajouter un booléen pour savoir si l’ingrédient chocolat est ajouté à ce dessert de même pour Chantilly. Puis, on crée des classes Crepe et Gaufre qui héritent de Dessert. Le calcul du prix des ingrédients est effectué dans la classe Dessert auquel on rajoute le prix spécifique du dessert suivant le type de l’objet (Gaufre ou Crepe). Cette solution semble plus satisfaisante mais pose toujours certains problèmes. Si l’on souhaite rajouter un ingrédient, il faut ajouter un attribut dans la classe Dessert et modifier la méthode qui calcule le prix afin de le prendre en considération. De plus, toutes les classes héritant de Dessert posséderont ces attributs qui n’auront pas toujours de sens. Si on crée une classe SaladeDeFruit héritant de Dessert on aura un attribut (hérité de la classe mère) nommé chocolat (bizarre pour une salade de fruit).

Voyons ce qu’apporte le pattern décorateur à notre problématique.


Implémentation d’une solution basée sur Décorateur

Diagramme UML de l'implémentation en Java du design pattern Décorateur

La solution consiste à utiliser à la fois l’héritage et la composition. Une classe abstraite Dessert regroupe les attributs et les méthodes communes. Puis, des desserts concrets tel que Gaufre et Crepe héritent de cette classe. Dans le constructeur de ces classes on met à jour les attributs défini dans Dessert à l’aide des accesseurs. Afin de gérer les ingrédients, il faut une classe abstraite nommée DecorateurIngredient. Celle-ci possède un Dessert en attribut et oblige la redéfinission de deux méthodes getLibelle() et getPrix(). Chaque ingrédient (Chantilly, Chocolat...) doit hériter de la classe DecorateurIngredient. Le constructeur de ces classes permet d’initialiser l’attribut dessert présent dans la classe mère. De plus, la redéfinition des méthodes getLibelle() et getPrix() va permettre d’ajouter des fonctionnalités. Pour comprendre comment cela fonctionne voyons le code Java correspondant au diagramme UML.


Implémentation de la classe abstraite Dessert

// Classe abstraite dessert.
public abstract class Dessert
{
        private String libelle;// Libellé du dessert.
        private double prix;// Prix du dessert.
       
        // Accesseurs en lecture pour le libellé et le prix.
        public String getLibelle()
        {
                return libelle;
        }
        public double getPrix()
        {
                return prix;
        }
       
        // Accesseurs en écriture pour le libellé et le prix.
        protected void setLibelle(String libelle)
        {
                this.libelle = libelle;
        }
        protected void setPrix(double prix)
        {
                this.prix = prix;
        }
       
        // Méthode utilisée pour l'affichage d'un dessert.
        public String toString()
        {
                NumberFormat format=NumberFormat.getInstance();
                format.setMinimumFractionDigits(2);// 2 chiffres après la virgule suffisent pour l'affichage.
                return getLibelle()+" : "+format.format(getPrix())+"€";
        }
}


La classe abstraite Dessert possède deux attributs que sont libelle et prix. Le premier correspond au nom du dessert sélectionné et le deuxième au prix d’achat. Des accesseurs en lecture/écriture permettent d’accéder à ces attributs. Afin de faciliter l’affichage dans la console, nous avons implémenter la méthode toString(). Celle-ci sera appelée automatiquement lors de l’affichage d’un objet de type Dessert dans la console.


Implémentation des classes Gaufre et Crepe

// Classe gaufre qui hérite de dessert
public class Gaufre extends Dessert
{
        // Constructeur qui intialise le libellé et le prix.
        public Gaufre()
        {
                setLibelle("Gaufre");
                setPrix(1.80);
        }
}
// Classe crêpe qui hérite de dessert.
public class Crepe extends Dessert
{
        // Constructeur qui initialise le libellé et le prix.
        public Crepe()
        {
                setLibelle("Crêpe");
                setPrix(1.50);
        }
}


Les classes Gaufre et Crepe sont des desserts concrets et donc héritent de Dessert. Leurs constructeurs permettent de mettre à jour le libellé et le prix (grâce aux accesseurs en écriture). On constate qu’il sera facile de rajouter un nouveau dessert concret sans modifier notre modèle.


Implémentation de la classe abstraite DecorateurIngredient

// Classe abstraite decorateurIngredient qui hérite de dessert.
public abstract class DecorateurIngredient extends Dessert
{
        protected Dessert dessert;// Dessert sur leuquel on applique l'ingrédient.
       
        // On oblige les ingrédients à implémenter la méthode getLibelle().
        public abstract String getLibelle();
        // On oblige les ingrédients à implémenter la méthode getPrix().
        public abstract double getPrix();
}


DecorateurIngredient va permettre de décorer les desserts avec différents ingrédients. Il s’agit d’une classe abstraite héritant de dessert. Celle-ci possède en attribut le dessert qu’elle va décorer (c’est à dire ajouter des fonctionnalités). A noter, que ce dessert peut correspondre à un dessert déjà décoré puisque celui-ci hérite indirectement de la classe Dessert. Le DecorateurIngredient oblige également la redéfinition des deux méthodes getLibelle() et getPrix() dans ses sous classes.


Implémentation des classes Chantilly et Chocolat

//Classe chantily qui hérite de decorateurIngredient et donc indirectement de dessert.
public class Chantilly extends DecorateurIngredient
{
        // Constructeur qui prend en paramètre le dessert.
        public Chantilly(Dessert d)
        {
                dessert = d;
        }
       
        // On affiche le libellé du dessert et on rajoute le libellé de l'ingrédient chantilly.
        public String getLibelle()
        {
                return dessert.getLibelle()+", chantilly";
        }
       
        // On additionne le prix du dessert et le prix de l'ingrédient chantilly.
        public double getPrix()
        {
                return dessert.getPrix()+0.50;
        }
}
// Classe chocolat qui hérite de decorateurIngredient et donc indirectement de dessert.
public class Chocolat extends DecorateurIngredient
{
        // Constructeur qui prend en paramètre le dessert.
        public Chocolat(Dessert d)
        {
                dessert = d;
        }
       
        // On affiche le libellé du dessert et on rajoute le libellé de l'ingrédient chocolat.
        public String getLibelle()
        {
                return dessert.getLibelle()+", chocolat";
        }
       
        // On additionne le prix du dessert et le prix de l'ingrédient chocolat.
        public double getPrix()
        {
                return dessert.getPrix()+0.20;
        }
}


Les classes Chantilly et Chocolat correspondent à deux ingrédients qui peuvent être ajoutés aux desserts. Pour cela ces classes héritent de DecorateurIngredient. Leurs constructeurs prennent en paramètre le dessert « nature » qui sera stocké dans l’attribut de DecorateurIngrédient. On note, que l’attribut dessert de la classe mère est déclaré en « protected » ce qui nous permet de se passer des accesseurs. Les méthodes getLibelle() et getPrix() sont redéfinies et ainsi on ajoute des fonctionnalités. Par exemple la méthode getLibelle() affiche le libellé du dessert en rajoutant le libellé de l’ingrédient. Le même principe est utilisé pour la méthode getPrix() pour le calcul du prix.


Implémentation de la classe Main

// Classe principale de l'application.
public class Main
{
        // Méthode principale.
        public static void main(String[] args)
        {
                // Création et affichage d'une gaufre au chocolat.
                Dessert d1 = new Gaufre();
                d1 = new Chocolat(d1);
                System.out.println(d1);
                // Création et affichage d'une crêpe au chocolat et chantilly.
                Dessert d2 = new Crepe();
                d2 = new Chocolat(d2);
                d2 = new Chantilly(d2);
                System.out.println(d2);
        }
}


La classe principale de notre application fabrique deux desserts. Une gaufre au chocolat et une crêpe au chocolat et à la chantilly. On affiche alors le libellé de ces desserts et leurs prix.


Résultat

Gaufre, chocolat : 2,00€
Crêpe, chocolat, chantilly : 2,20€


Comme souhaité on obtient un libellé complet et un prix tenant compte de tous les ingrédients ajoutés. A noter, que l’on peut décorer un dessert avec un nombre quelconque d’ingrédients. Il est même possible d’utiliser plusieurs fois le même ingrédient (une petite crêpe avec un double supplément de chocolat pour les gourmands...).


Conclusion

Le pattern Décorateur basé sur l’héritage et la composition est facile à mettre place. Il permet de répondre parfaitement à la problématique rencontrée lors de la conception de cette application de gestion des desserts. C’est à dire comment concevoir une application évolutive ou l’ajout et la modification de fonctionnalités ne posera pas de problème (explosion du nombre de classe...).

L’inconvénient de ce pattern est la multiplicité des objets qu’il faut créer pour l’utiliser. On peut le remarquer avec le nombre de « new » présent dans la classe Main. Pour résoudre ce problème on pourra combiner Décorateur avec un autre pattern tel que Fabrique ou Monteur.

Code source: 
Commentaires

Bonjour, merci pour vos excellents tutoriaux, serait il-possible d’avoir l’exemple de design pattern décorateur associé au design pattern fabrication...

ps la classe abstraite dessert ne possede aucune méthode abstraite, n’en faudrait -il pas au moins une ? meric encore s.h

Salut Merci pour votre explication

s’il Vous plait j’ai un question Pourquoi tu as mis une classe abstraite entre les objets (chantilly,chocolat) et la super-classe dessert ? queller l’utilité ? merci de me repondre

Est Ce Que Ce que Est Correct

en Supprime La class abstrait DecorateurIngredient S-V-D :

public class Chantilly extends Dessert

private Dessert dessert ;

public Chantilly(Dessert d)

dessert = d ;

Et la Meme pour Class pour Chocolat

Pourquoi on ne fait pas une super-classe Ingredient et des sous-classe comme Chocolat , Chantilly etc... Puis une super-classe Dessert avec comme attribut une liste d’ingrédients et des sous classe comme Crepe , Gateau etc.. ? Les ingrédients ayant comme attributs un nom et un prix.

Bonjour,

Pour reprendre l’utilisation d’ "instanceof" sur un objet "décoré".

Prenons un morceaux de ton code de la classe Main, tel qu’il est dans l’exemple :

Dessert d1 = new Gaufre() ;
d1 = new Chocolat(d1) ;

Le problème avec le design pattern Decorator, c’est que tu ne peux pas récupérer le type Gaufre.

if(d1 instanceof Gaufre) // Retourne faux.
if(d1 instanceof Chocolat) //Retourne vrai.

Question : Quel design pattern appliquer lorsque tu effectues des traitements différents en fonction du type ?

Bonjour et bonne année !

Un grand merci pour ce tuto et bravo pour ce grand sens pédagogique ...

Après avoir créé nos classe une fois dans la classe de test :

Si j’ai bien compris on crée un dessert nature, on initialise un premier décore (en lui donnant comme param le dessert nature) et on stock ceci dans le dessert nature même (la case mémoire qui s’adressait au dessert nature désormais s’adresse à ce nouvel dessert) ...

Pour la gouffre on s’arrête là mais pour la crêpe on continue toujours de même manière..

est-ce bien ça ou je suis à côté de la plaque ??!!

en gros si l’on ne reprenait pas la même variable ce serait comme ça :

Dessert d1 = new Gaufre() ;
Dessert dchoco = new Chocolat(d1) ;
System.out.println(dchoco ) ;
Dessert d2 = new Crepe() ;
Dessert dcrep = new Chocolat(d2) ;
Dessert dcrep2 = new Chantilly(dcrep) ;
System.out.println(dcrep2 ) ;

c’est ça ??!!

merci de me répondre

une passionnée de Java :-))

Bonjour,

Merci de ta réponse.

J’ai essayé sur Eclipse, la version du code que j’avais envoyé, le résultat est la même que la tienne. Est-ce que c’est par hasard ou est-ce que ces deux version disent la même chose ?

... et une petite question encore :

Pour l’utilisation de ce pattern comment on choisi les rôle ? Pour l’exemple de dessert est-ce que par exemple dans un autre scénario on peut inverser les rôles : une classe DecorateurBase aura les classes Crepe et Gouffre qui l’étendent et qui prennent comme param un objet du type Chantilly ou du type Chocholat qui étendent du classe abstrait Ingrediant (DecorateurBase extends Ingrediant et aura un attribut du type Ingrediant)

enfin les rôles inversés mais toujours la vedette est l’objet qui aura comme param un autre objet.

oui ou non ?!

merci de me répondre

pationnée du java

Bonjour,

Merci beaucoup de ta réponse et de cette clé magique :

De manière générale les objets concrets peuvent exister sans décorateurs ....

Savoir ça, aide beaucoup à établir son scénario ;-)

Sur ce je m’en vais continuer avec ton tuto Oservateur !

merci encore

Bonjour Matthieu,

merci bien pour cet exemple.

J’ai un problème avec la terminologie, cependant, qui a constitué un petit obstacle dans ma compréhension du pattern.

Dans la sémantique de l’exemple, "chocolat" n’est pas un "dessert", mais un "dessert (quelconque) décoré de chocolat".

De façon plus abstraite, la classe "Decorateur" du pattern ne me semble pas être pas un "Composant", mais un "Composant décoré", ou encore, la classe "Decorateur Concret" ne me semble pas être un "Composant", mais un "Composant (quelconque) décoré de decorateur concrêt".

Ne serait il pas plus clair d’appeler : "decorateur" avec "composant décoré" et "decorateur concrêt" avec "composant décore de qqchose"

Un autre exemple courant du pattern est celui relatif à une hiérarchie de véhicules, dans laquelle j’ai déjà vu une classe-décoratrice nommée "Galerie de toit". Or... Une "Galerie de toit" n’est pas un véhicule ! Pourquoi donc ne pas l’appeler "véhicule avec galerie" ?

Que pensez vous de ces quelques considérations terminologiques ?

Bonjour,

Très bonne explication du pattern et sympathique qui plus est.

Cependant je me posais une question , par exemple si chocolat devient une classe abstraite par exemple et que celle-ci est la généralisation de chocolat blanc, chocolat noir et chocolat au lait par exemple. Comment cela pouvait s’implémenter ?

Une seconde, si mon objet devient de type concret chantilly et si chantilly possède une méthode public qui lui est propre, pourquoi ne pourrais je pas accéder à celle-ci ?

Encore merci et bonne continuation.

Bonjour, merci pour cet article qui est très clair :)

Je suis actuellement confronté à un problème de traitement différent selon le type de l’objet (ce fameux instanceof), et je confirme que c’est effectivement un truc à ne pas faire !

Ce qui amène à ma question, quelles sont les techniques habituelles pour éviter d’avoir à tester le type ? Je sais que cette question est un peu vague... Pour l’instant, tout ce que je vois comme solution, c’est de s’assurer du type en amont dans l’application, mais cela me parait un peu léger. Donc voila, si tu as des patterns ou autre permettant de pallier à ce problème classique, cela m’intéresserait beaucoup

à plus

Bonjour,

Un grand merci pour ce tuto très bien fait ! J'ai une question sur le pattern Décorateur.

Un pattern décorateur permet d'ajouter dynamiquement des comportants à un objet mais peut t-il en enlever ? Je m'explique :p

Soit une crèpe au chocolat et à la chantilly : Dessert dessert = new Chocolat( new Chantilly ( new Crepe() )

Peut-ont décider d'enlever dynamiquement la chantilly sur notre dessert ? Comment peut-on le faire ?

 

Bonjour, et merci pour cet exemple très clair!

Je me trouve actuellement face un problème assez proche de celui de votre exemple. Je pense que l'utilisation d'un patron décorateur facilitera la maintenance et favorisera les possiblités d'étendre le code.

Cependant, il y a quelque chose que j'ai des difficultés à réaliser. Imaginons que les différents ingrédients chocolats,chantilly,... aient en commun une méthode qui peut différer selon un choix. Par exemple, on aurait un résultat différent au getPrix() selon si la chantilly est surgelée ou non. On aurait donc en commun le fait que les ingrédients peuvent être surgelés ou non et que le prix varie en fonction de ça.

La solution d'avoir différentes classes chantillySurgelee, chantillyFraiche, etc... ne me paraît pas une bonne solution si on veut factoriser le code getPrixSurgele / getPrixFrais.

Je ne sais pas si j'ai été clair, donc je vais essayer de formuler les choses autrement, toujours avec l'exemple du dessert:

- j'ai une méthode getPrix qui peut avoir différentes implémentations (getPrixSurgele, getPrixFrais, ...)

- cette méthode peut être commune à plusieurs ingrédients (pas nécessairement tous, disons par exemple que la chantilly ne peut pas être surgelée, mais que le chocolat et la glace peuvent être les deux)

Voyez-vous une solution réalisable et facilement maintenable/extensible à mon problème (si je l'ai clairement expliqué, n'hésitez pas à me demander des détails...)

 

Merci beaucoup!

Bonjour,

La solution pour laquelle j'ai opté se rapproche de ta première proposition. J'ai allié un patron stratégie à mon patron décorateur. Dans cet exemple, le dessert contiendrait alors un champ stratégie et une méthode getPrix. Le champ stratégie contenant un pointeur vers une classe abstraite permettant d'implémenter les différents getPrix, et la méthode getPrix un appel à cette stratégie.

Merci pour ta réponse! =)

Bonjour,

Pour gerer le probleme d'instanceof, pourquoi ne pas creer une methode public dans la classe dessert isInstanceOf(Class c). L'objet servant la construction du decorateur est conserve dans une variable membre qui sera null lors de la premiere construction du dessert, a force de decoration on conserve la hierarchie des objets.

il suffit alors d'implementer la methode isInstanceOf comme methode concrete dans la classe abstraite testant la classe passer en parametre juqu'a la trouver ou quelle soit null.

Imaginons qu'un programme dont l'implementation est inconnue vous donne une liste de desserts et que suivant les type de dessert, il faille les transporter d'un facon differrente : Tout ce qui contient du chocolat + les toutes les crepes en camion frigo, le reste en camion normal.

Il faudra bien interroger les objets sur leur type d'instance. pour les classer.....

public abstract class Dessert
{
        private String libelle;// Libellé du dessert.
            protected Dessert originalDessert = null;
       
        public Dessert() {}
        public Dessert(Dessert d) {originalDessert = d;}
 
            public isInstanceOf(Class c) {
                Dessert current = this;
                while (current != null) {
                   if (current instanceof c) {
                     return true;
                   }
                   current = originalDessert ;
                 }
                 return false;
            }
            .....
}
 
public class Chocolat extends DecorateurIngredient
{
         //PLUS BESOIN DE CONSTRUCTEUR !!!!
 
        // On affiche le libellé du dessert et on rajoute le libellé de l'ingrédient chocolat.
        public String getLibelle()
        {
                return originalDessert.getLibelle()+", chocolat";
        }
        
        // On additionne le prix du dessert et le prix de l'ingrédient chocolat.
        public double getPrix()
        {
                return originalDessert.getPrix()+0.20;
        }
}
 

Bonjour,

Bon exemple pour illustrer ce pattern !

Y aurait-il un autre pattern de caché dans le diagramme de classe ? Je pense à celui du composite.

salut 

est ce vous pouvez m'aider de resoudre ce probléme sans utilisation de patron de conception Decorator , meme si la solution favorable c'est celle ci mais je veut voir la défference .

Merci en avance.

Ajouter un commentaire