Patron Décorateur
Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 93 sur 107.
Le patron Décorateur vous permet d'ajouter dynamiquement de nouveaux comportements à des objets en les enveloppant dans d'autres objets. Alors que l'Adaptateur modifie une interface pour qu'elle corresponde à une autre, le Décorateur conserve la même interface mais améliore les fonctionnalités en superposant des enveloppes autour de l'objet original.
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é la tâche :
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 Décorateur réside dans l'empilement de plusieurs wrappers 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 à des objets sans modifier leur code, particulièrement 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 de café en utilisant le motif de conception Decorator ! Vous allez créer une boisson au café de base et des décorateurs qui ajoutent des suppléments comme du lait, du sucre et de la crème fouettée. Chaque décorateur enveloppe le précédent, construisant à la fois la description et le prix — un exemple concret parfait de la façon dont les décorateurs superposent les fonctionnalités.
Vous organiserez votre code sur trois fichiers :
beverage.go: Définissez votre interface principale et le type de café de base.Créez une interface
Beverageavec deux méthodes :Description() string— retourne ce qu'est la boissonCost() float64— retourne le prix
Implémentez une structure
Coffeecomme boisson de base. Sa description doit êtreCoffeeet son coût doit être2.00.decorators.go: Créez vos types de décorateurs qui ajoutent des suppléments à n'importe quelle boisson.Chaque décorateur enveloppe une
Beverageet implémente la même interface. Construisez 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 améliorer le résultat.
main.go: Construisez des boissons personnalisées en empilant les décorateurs.Lisez un nombre de suppléments à ajouter. Ensuite, pour chaque supplément, lisez son type (
milk,sugar, ouwhip) et enveloppez votre boisson actuelle avec le décorateur approprié.Après avoir appliqué tous les suppléments, affichez la description et le coût final de la boisson sur des lignes séparées. Formatez le coût comme
$X.XXavec exactement deux décimales.
Les entrées suivantes seront fournies :
- Ligne 1 : Nombre de suppléments à ajouter
- Lignes suivantes : Un type de supplément par ligne (
milk,sugar, ouwhip)
Par exemple, avec :
2
milk
sugarVotre sortie devrait être :
Coffee, Milk, Sugar
$2.75Et avec :
3
whip
milk
milkVotre sortie devrait être :
Coffee, Whip, Milk, Milk
$3.75Et avec :
0Votre sortie devrait être :
Coffee
$2.00Remarquez comment chaque décorateur enveloppe la boisson précédente, construisant à la fois la description et le coût. Vous pouvez ajouter le même supplément plusieurs fois (comme un double lait), et l'ordre des décorateurs détermine l'ordre dans la description. Le café de base ne sait rien des suppléments — chaque décorateur améliore simplement ce qu'il enveloppe !
Aide-mémoire
Le patron Décorateur ajoute dynamiquement de nouveaux comportements aux objets en les enveloppant dans d'autres objets. Il conserve la même interface tout en améliorant les fonctionnalités par superposition.
Le type de base et les décorateurs implémentent tous deux 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 objet et ajoutent un comportement avant ou après la délégation :
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))
}Empilez plusieurs décorateurs pour combiner les comportements :
notifier := BasicNotifier{}
withTimestamp := TimestampDecorator{wrapped: notifier}
withBoth := UppercaseDecorator{wrapped: withTimestamp}
fmt.Println(notifier.Send("Hello"))
// Sending: Hello
fmt.Println(withBoth.Send("Hello"))
// SENDING: [2024-01-15] HELLOUtilisez le Décorateur pour ajouter des responsabilités sans modifier le code de l'objet, en particulier pour différentes combinaisons de fonctionnalités. Utilisations courantes : journalisation (logging), mise en cache, authentification, compression.
Essayez vous-même
package main
import (
"fmt"
)
func main() {
// Lire le nombre de suppléments
var count int
fmt.Scanln(&count)
// Commencer avec un café de base
var drink Beverage = &Coffee{}
// Lire chaque supplément et envelopper la boisson avec le décorateur approprié
for i := 0; i < count; i++ {
var extra string
fmt.Scanln(&extra)
// TODO: Selon le type de supplément, 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 vs non exportésIntroduction à la POO en GoLes Structs comme ClassesDéfinir des méthodes sur les StructsReceveurs par pointeur vs par valeurInitialisation de StructsFonctions constructeursRécapitulatif - Calculatrice simple4Interfaces
Introduction aux interfacesImplémentation impliciteL'interface comme contratInterface vide (any)Assertion de typeType SwitchComposition d'interfacesInterfaces Stringer & ErrorRécapitulatif - Calculateur de formes7Encapsulation
Champs exportés vs non exportésEncapsulation au niveau du packageMéthodes Getter & SetterMasquage d'informations en GoRécapitulatif - Dossiers étudiants10Génériques (Go 1.18+)
Introduction aux génériquesParamètres de typeContraintes de typeStructures génériquesContournement pour les méthodes génériquesRécapitulatif - Collections génériques13Patrons de conception, Partie 1
Introduction aux Design PatternsPatron SingletonPatron FactoryPatron Abstract FactoryPatron ObserverPatron Strategy2Exploration approfondie des Types et Structs
Types de base et compositesDéfinitions de types personnalisésTags de StructStructs anonymesStructs imbriquéesValeurs par défaut et Zero ValuesRécapitulatif - Carnet de contacts5Composition plutôt qu'héritage
Pourquoi Go n'a pas d'héritageLes bases de l'imbrication de structsPromotion de méthodesImbriquer plusieurs structsImbrication vs AgrégationMasquage de méthodes imbriquéesRécapitulatif - Hiérarchie des employés8Gestion des erreurs & POO
L'interface errorTypes d'erreurs personnalisésError Wrapping (fmt.Errorf)Erreurs sentinelleserrors.Is() et errors.As()Panic, Defer et RecoverRécapitulatif - File Parser11Bibliothèque standard et POO
io.Reader & io.WriterL'interface sort.InterfaceL'interface fmt.Stringerencoding/json avec les StructsL'interface http.HandlerRécapitulatif - Modèles d'API REST14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeMiddleware en tant que Décorateur3Pointeurs et Mémoire
Bases des pointeurs en GoPointeurs vers des structsPassage par valeur vs référenceLa fonction new()Garbage Collection en GoRécapitulatif - Constructeur de liste chaînée6Le polymorphisme en Go
Le polymorphisme via les interfacesLe Duck Typing en GoRègles de satisfaction des interfacesCollections polymorphesInjection de dépendancesRécapitulatif - Processeur de paiement9Concurrence et POO
Bases des GoroutinesChannels et CommunicationChannels avec buffer vs sans bufferL'instruction Selectsync.Mutex et sync.RWMutexsync.WaitGroupConception de structures Thread-SafeRécapitulatif - Worker Pool