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

Decorator appliqué aux options de livraison Commande appelle frais(6000) sur LivraisonOfferte, qui entoure EmballageCadeau, qui entoure Express. Express renvoie 1290, EmballageCadeau ajoute 300 et renvoie 1590, LivraisonOfferte renvoie 0 car la commande atteint 5000 centimes. Le total vaut 6000 centimes, livrés sous 2 jours. Commande Le client ne voit quel'enveloppe extérieure. frais(6000) LivraisonOfferte frais nuls dès 5000 EmballageCadeau +300 centimes, +1 jour Express 990 + 5 %, 1 jour L'objet entouréfait le calcul de base. renvoie 990 + 300 = 1290 renvoie 1290 + 300 = 1590 6000 ≥ 5000, renvoie 0 Total 6000 + 0 = 6000 centimes, sous 2 jours.Chaque enveloppe est aussi un ModeLivraisonet transmet l'appel à celle qu'elle entoure.

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.