Aide-mémoire, Head First Design Patterns, chapitre 3
Decorator
Une enveloppe entoure un objet, respecte le même contrat que lui et ajoute un comportement autour de ses appels. Les enveloppes s'emboîtent, et le client ne voit pas la différence.
« Attacher dynamiquement des responsabilités supplémentaires à un objet. Les décorateurs offrent une alternative souple à l'héritage pour étendre des fonctionnalités. » Gamma, Helm, Johnson et Vlissides, Design Patterns, 1994
Les rôles
- Le contrat,
ModeLivraison - L'objet entouré et toutes ses enveloppes le respectent.
- L'objet entouré,
Express - Il fait le calcul de base et ignore qu'on l'entoure.
- Les enveloppes,
LivraisonOfferte,EmballageCadeau - Chacune garde le mode reçu dans son constructeur, lui transmet l'appel, puis modifie le résultat.
Le client, Commande, reçoit l'enveloppe extérieure et s'en sert comme de n'importe quel mode : commande.recapitulatif(new LivraisonOfferte(new EmballageCadeau(new Express()))).
L'ordre des enveloppes change le résultat. Sur une commande de 6 000 centimes, l'ordre du schéma donne 6 000 centimes, car la livraison offerte annule aussi l'emballage. Dans l'ordre inverse, new EmballageCadeau(new LivraisonOfferte(new Express())), l'emballage reste payant et le total monte à 6 300 centimes.
Le reconnaître
Une option s'applique à n'importe quelle variante, et les options se combinent. Avec l'héritage, chaque combinaison devient une sous-classe. Pour Express, il faut ExpressCadeau, ExpressOfferte et ExpressCadeauOfferte, car une classe ne peut pas hériter des deux premières à la fois. Avec quatre modes, cela fait 12 classes. Avec Decorator, il en faut deux en tout, une par option, et chacune entoure n'importe quel mode.
Quand une seule classe a besoin d'un ajout, la modifier directement reste plus simple.
Le principe du chapitre
« Les classes doivent être ouvertes à l'extension, mais fermées à la modification. » L'emballage cadeau s'ajoute dans une nouvelle classe, sans toucher à Express ni à Commande.
L'enveloppe compose un objet au lieu d'en hériter, comme le recommandait déjà le chapitre 1.
Dans le code web
Une fonction qui reçoit fetch et renvoie une fonction de même signature est une enveloppe. Elle peut ajouter un en-tête d'authentification ou réessayer après une panne, et le code appelant ne voit pas la différence.
Un middleware de Hono s'exécute autour du gestionnaire de la route. Son code avant await next() agit sur la requête, son code après agit sur la réponse. Les middlewares s'empilent comme les enveloppes du schéma, et leur ordre compte de la même façon.
Le piège
Les décorateurs de TypeScript, écrits @Component ou @Injectable dans Angular, portent le même nom. Ils annotent une classe au moment de sa déclaration et ne créent aucune enveloppe autour d'un objet.
Une enveloppe doit transmettre chaque méthode du contrat. Si LivraisonOfferte oubliait de transmettre delaiJours, le client recevrait un délai faux sans aucune erreur.
À vous
La boutique propose une assurance qui ajoute 2 % du sous-total aux frais, avec n'importe quel mode. Quelles classes faut-il modifier ?
Voir la réponse
Aucune. On écrit une nouvelle enveloppe Assurance qui implémente ModeLivraison, garde le mode reçu et ajoute Math.round(sousTotal * 0.02) à ses frais. Commande, les modes et les autres enveloppes restent tels quels.