Modèle Décorateur
Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy. Leçon 93 sur 107.
Le pattern Decorator vous permet d’ajouter dynamiquement un nouveau comportement aux objets en les enveloppant dans d’autres objets. Alors que Adapter modifie une interface pour l’adapter à une autre, Decorator conserve la même interface, mais améliore les fonctionnalités en superposant des enveloppes autour de l’objet d’origine.
En Go, nous implémentons cela en faisant en sorte que le type de base et les décorateurs implémentent la même interface :
type Notifier interface {
Send(message string) string
}
type BasicNotifier struct{}
func (b BasicNotifier) Send(message string) string {
return "Sending: " + message
}Les décorateurs enveloppent un autre Notifier et ajoutent un comportement avant ou après lui avoir délégué l’exécution :
type TimestampDecorator struct {
wrapped Notifier
}
func (t TimestampDecorator) Send(message string) string {
timestamped := "[2024-01-15] " + message
return t.wrapped.Send(timestamped)
}
type UppercaseDecorator struct {
wrapped Notifier
}
func (u UppercaseDecorator) Send(message string) string {
return strings.ToUpper(u.wrapped.Send(message))
}La puissance du modèle Decorator réside dans l’empilement de plusieurs enveloppes pour combiner les comportements :
notifier := BasicNotifier{}
withTimestamp := TimestampDecorator{wrapped: notifier}
withBoth := UppercaseDecorator{wrapped: withTimestamp}
fmt.Println(notifier.Send("Hello"))
// Envoi : Hello
fmt.Println(withBoth.Send("Hello"))
// ENVOI : [2024-01-15] HELLOLe décorateur est idéal lorsque vous devez ajouter des responsabilités aux objets sans modifier leur code, en particulier lorsque différentes combinaisons de fonctionnalités sont nécessaires. Il est couramment utilisé pour les couches de journalisation, de mise en cache, d’authentification et de compression.
Défi
FacileConstruisons un système de commande pour un café en utilisant le pattern Decorator ! Tu vas créer une boisson à base de café et des décorateurs qui ajoutent des extras comme du lait, du sucre et de la crème fouettée. Chaque décorateur enveloppe le précédent, en construisant à la fois la description et le prix : un parfait exemple concret de la façon dont les décorateurs ajoutent des fonctionnalités par couches.
Tu organiseras ton code sur trois fichiers :
beverage.go: définis ton interface principale et ton type de café de base.Crée une interface
Beverageavec deux méthodes :Description() string: renvoie ce qu'est la boissonCost() float64: renvoie le prix
Implémente une structure
Coffeecomme boisson de base. Sa description doit êtreCoffeeet son coût doit être2.00.decorators.go: crée tes types de décorateurs qui ajoutent des extras à n'importe quelle boisson.Chaque décorateur enveloppe un
Beverageet implémente la même interface. Construis trois décorateurs :MilkDecorator: ajoute, Milkà la description et0.50au coûtSugarDecorator: ajoute, Sugarà la description et0.25au coûtWhipDecorator: ajoute, Whipà la description et0.75au coût
Chaque décorateur doit appeler les méthodes de la boisson enveloppée et enrichir le résultat.
main.go: construis des boissons personnalisées en empilant les décorateurs.Lis un nombre d'extras à ajouter. Ensuite, pour chaque extra, lis son type (
milk,sugarouwhip) et enveloppe ta boisson actuelle avec le décorateur approprié.Après avoir appliqué tous les extras, affiche la description et le coût de la boisson finale sur des lignes séparées. Formate le coût sous la forme
$X.XXavec exactement deux décimales.
Les entrées suivantes seront fournies :
- Ligne 1 : nombre d'extras à ajouter
- Lignes suivantes : un type d'extra par ligne (
milk,sugarouwhip)
Par exemple, avec :
2
milk
sugarTa sortie doit être :
Coffee, Milk, Sugar
$2.75Et avec :
3
whip
milk
milkTa sortie doit être :
Coffee, Whip, Milk, Milk
$3.75Et avec :
0Ta sortie doit être :
Coffee
$2.00Remarque comment chaque décorateur enveloppe la boisson précédente, en construisant à la fois la description et le coût. Tu peux ajouter le même extra plusieurs fois (comme une double dose de lait), et l'ordre des décorateurs détermine l'ordre dans la description. Le café de base ne sait rien des extras : chaque décorateur enrichit simplement ce qu'il enveloppe !
Essayez vous-même
package main
import (
"fmt"
)
func main() {
// Lire le nombre d'extras
var count int
fmt.Scanln(&count)
// Commencer avec un café de base
var drink Beverage = &Coffee{}
// Lire chaque extra et envelopper drink avec le décorateur approprié
for i := 0; i < count; i++ {
var extra string
fmt.Scanln(&extra)
// TODO: En fonction du type d'extra, envelopper drink avec le décorateur approprié
// Utiliser MilkDecorator pour "milk"
// Utiliser SugarDecorator pour "sugar"
// Utiliser WhipDecorator pour "whip"
}
// TODO: Afficher la description finale et le coût
// Formater le coût comme $X.XX avec exactement deux décimales
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO en Go
Fichiers externesEspace de travail et modules GoPackages et importsNoms exportés et non exportésIntroduction à la POO en GoStructs comme classesDéfinir des méthodes sur des structsRécepteurs pointeurs ou par valeurInitialisation des structsFonctions constructeursRécapitulatif - Calculatrice simple4Interfaces
Introduction aux interfacesImplémentation impliciteL’interface comme contratInterface vide (any)Assertion de typeCommutation de typeComposition d’interfacesInterfaces Stringer et ErrorRécapitulatif - Calculateur de formes7Encapsulation
Champs exportés vs non exportésEncapsulation au niveau du packageMéthodes Getter et SetterDissimulation de l’information en GoRécapitulatif – Fiches d’étudiants10Génériques (Go 1.18+)
Introduction aux génériquesParamètres de typeContraintes de typeStructures génériquesSolution de contournement pour les méthodes génériquesRécapitulatif - Collection générique2Plongée approfondie dans les types et les structs
Types de base et compositesDéfinitions de types personnalisésTags de structStructs anonymesStructs imbriquésValeurs zéro et valeurs par défautRécapitulatif - Carnet de contacts5La composition plutôt que l’héritage
Pourquoi Go n’a pas d’héritageBases de l’inclusion de structsPromotion des méthodesInclure plusieurs structsInclusion ou agrégationMasquage des méthodes inclusesRécapitulatif : hiérarchie des employés8Gestion des erreurs et POO
L’interface errorTypes d’erreurs personnalisésEnrobage des erreurs (fmt.Errorf)Erreurs sentinelleserrors.Is() et errors.As()Panic, Defer et RecoverRécapitulatif - Analyseur de fichiers11Bibliothèque standard et POO
io.Reader et io.Writersort.InterfaceInterface fmt.Stringerencoding/json avec des structsInterface http.HandlerRécapitulatif - Modèles d’API REST14Modèles de conception – Partie 2
Modèle CommandeModèle AdaptateurModèle DécorateurModèle Méthode modèleModèle ÉtatModèle CompositeMiddleware comme décorateur3Pointeurs et mémoire
Notions de base des pointeurs en GoPointeurs vers des structuresPassage par valeur ou par référenceLa fonction new()Garbage collection en GoRécapitulatif - Constructeur de listes chaînées6Polymorphisme en Go
Polymorphisme via les interfacesDuck typing en GoRègles de satisfaction des interfacesCollections polymorphesInjection de dépendancesRécapitulatif – Processeur de paiements9Concurrence et POO
Bases des GoroutinesCanaux et communicationCanaux tamponnés ou non tamponnésInstruction selectsync.Mutex et sync.RWMutexsync.WaitGroupConception de structures thread-safeRécapitulatif - Pool de workersEntraînez-vous par vous-même : Compilateur Go en ligne