Decorator-Muster
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 93 von 107.
Das Decorator-Muster ermöglicht es Ihnen, Objekten dynamisch neues Verhalten hinzuzufügen, indem Sie sie in andere Objekte einhüllen. Während der Adapter eine Schnittstelle ändert, um sie an eine andere anzupassen, behält der Decorator dieselbe Schnittstelle bei, erweitert jedoch die Funktionalität, indem er Wrapper um das ursprüngliche Objekt schichtet.
In Go implementieren wir dies, indem sowohl der Basistyp als auch die Dekoratoren dasselbe Interface implementieren:
type Notifier interface {
Send(message string) string
}
type BasicNotifier struct{}
func (b BasicNotifier) Send(message string) string {
return "Sending: " + message
}Decorators umschließen einen anderen Notifier und fügen Verhalten hinzu, bevor oder nachdem sie an diesen delegieren:
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))
}Die Stärke des Decorators liegt im Stapeln mehrerer Wrapper, um Verhaltensweisen zu kombinieren:
notifier := BasicNotifier{}
withTimestamp := TimestampDecorator{wrapped: notifier}
withBoth := UppercaseDecorator{wrapped: withTimestamp}
fmt.Println(notifier.Send("Hello"))
// Senden: Hello
fmt.Println(withBoth.Send("Hello"))
// SENDEN: [2024-01-15] HELLODas Decorator-Muster ist ideal, wenn Sie Objekten Verantwortlichkeiten hinzufügen müssen, ohne deren Code zu ändern, insbesondere wenn verschiedene Kombinationen von Funktionen benötigt werden. Es wird häufig für Logging-, Caching-, Authentifizierungs- und Komprimierungsschichten verwendet.
Aufgabe
EinfachLassen Sie uns ein Bestellsystem für ein Café mit dem Decorator-Muster erstellen! Sie werden ein Basis-Kaffeegetränk und Dekoratoren erstellen, die Extras wie Milch, Zucker und Schlagsahne hinzufügen. Jeder Dekorator umschließt den vorherigen und baut sowohl die Beschreibung als auch den Preis auf – ein perfektes Praxisbeispiel dafür, wie Dekoratoren Funktionalität schichten.
Sie werden Ihren Code auf drei Dateien verteilen:
beverage.go: Definieren Sie Ihr Kern-Interface und den Basis-Kaffeetyp.Erstellen Sie ein
Beverage-Interface mit zwei Methoden:Description() string— gibt an, was das Getränk istCost() float64— gibt den Preis zurück
Implementieren Sie ein
Coffee-Struct als Ihr Basisgetränk. Seine Beschreibung sollteCoffeesein und seine Kosten sollten2.00betragen.decorators.go: Erstellen Sie Ihre Dekorator-Typen, die jedem Getränk Extras hinzufügen.Jeder Dekorator umschließt ein
Beverageund implementiert dasselbe Interface. Erstellen Sie drei Dekoratoren:MilkDecorator— fügt, Milkzur Beschreibung und0.50zu den Kosten hinzuSugarDecorator— fügt, Sugarzur Beschreibung und0.25zu den Kosten hinzuWhipDecorator— fügt, Whipzur Beschreibung und0.75zu den Kosten hinzu
Jeder Dekorator sollte die Methoden des umschlossenen Getränks aufrufen und das Ergebnis erweitern.
main.go: Erstellen Sie maßgeschneiderte Getränke, indem Sie Dekoratoren stapeln.Lesen Sie eine Anzahl von Extras ein, die hinzugefügt werden sollen. Lesen Sie dann für jedes Extra dessen Typ (
milk,sugaroderwhip) ein und umschließen Sie Ihr aktuelles Getränk mit dem entsprechenden Dekorator.Geben Sie nach dem Anwenden aller Extras die Beschreibung und die Kosten des fertigen Getränks in separaten Zeilen aus. Formatieren Sie die Kosten als
$X.XXmit genau zwei Dezimalstellen.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Anzahl der hinzuzufügenden Extras
- Folgende Zeilen: Ein Extra-Typ pro Zeile (
milk,sugaroderwhip)
Zum Beispiel, bei folgender Eingabe:
2
milk
sugarSollte Ihre Ausgabe wie folgt aussehen:
Coffee, Milk, Sugar
$2.75Und bei folgender Eingabe:
3
whip
milk
milkSollte Ihre Ausgabe wie folgt aussehen:
Coffee, Whip, Milk, Milk
$3.75Und bei folgender Eingabe:
0Sollte Ihre Ausgabe wie folgt aussehen:
Coffee
$2.00Beachten Sie, wie jeder Dekorator das vorherige Getränk umschließt und so sowohl die Beschreibung als auch die Kosten aufbaut. Sie können dasselbe Extra mehrmals hinzufügen (wie doppelte Milch), und die Reihenfolge der Dekoratoren bestimmt die Reihenfolge in der Beschreibung. Der Basis-Kaffee weiß nichts über die Extras – jeder Dekorator erweitert einfach das, was er umschließt!
Spickzettel
Das Decorator-Muster fügt Objekten dynamisch neues Verhalten hinzu, indem es sie in andere Objekte einpackt. Es behält die gleiche Schnittstelle bei, erweitert aber die Funktionalität durch Schichtung.
Sowohl der Basistyp als auch die Decorator implementieren dieselbe Schnittstelle:
type Notifier interface {
Send(message string) string
}
type BasicNotifier struct{}
func (b BasicNotifier) Send(message string) string {
return "Sending: " + message
}Decorator umschließen ein anderes Objekt und fügen Verhalten vor oder nach der Delegierung hinzu:
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))
}Stapeln Sie mehrere Decorator, um Verhaltensweisen zu kombinieren:
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] HELLOVerwenden Sie den Decorator, um Verantwortlichkeiten hinzuzufügen, ohne den Objektcode zu ändern, insbesondere für verschiedene Merkmalskombinationen. Häufige Anwendungen: Logging, Caching, Authentifizierung, Komprimierung.
Probier es selbst
package main
import (
"fmt"
)
func main() {
// Lies die Anzahl der Extras
var count int
fmt.Scanln(&count)
// Beginne mit einem Basis-Kaffee
var drink Beverage = &Coffee{}
// Lies jedes Extra und umhülle das Getränk mit dem entsprechenden Decorator
for i := 0; i < count; i++ {
var extra string
fmt.Scanln(&extra)
// TODO: Basierend auf dem Typ des Extras, umhülle drink mit dem entsprechenden Decorator
// Verwende MilkDecorator für "milk"
// Verwende SugarDecorator für "sugar"
// Verwende WhipDecorator für "whip"
}
// TODO: Gib die endgültige Beschreibung und die Kosten aus
// Formatiere die Kosten als $X.XX mit genau zwei Dezimalstellen
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der Go OOP
Externe DateienGo Workspace & ModulePackages & ImportsExportierte vs. nicht-exportierte NamenEinführung in OOP in GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktor-FunktionenZusammenfassung – Einfacher Taschenrechner4Interfaces
Einführung in InterfacesImplizite ImplementierungInterface als VertragLeeres Interface (any)Type AssertionType SwitchInterface-KompositionStringer & Error InterfacesRückblick - Formen-Rechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf Paket-EbeneGetter- & Setter-MethodenInformation Hiding in GoZusammenfassung - Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterType ConstraintsGenerische StructsWorkaround für generische MethodenZusammenfassung – Generische Collection2Vertiefung: Typen & Structs
Basis- & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct TagsAnonyme StructsVerschachtelte StructsZero Values & StandardwerteZusammenfassung - Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen des Struct EmbeddingMethod PromotionEinbetten mehrerer StructsEmbedding vs. AggregationShadowing eingebetteter MethodenZusammenfassung – Mitarbeiter-Hierarchie8Fehlerbehandlung & OOP
Das error-InterfaceEigene FehlertypenError Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverZusammenfassung – Datei-Parser11Standardbibliothek & OOP
io.Reader & io.Writersort.Interfacefmt.Stringer Interfaceencoding/json mit Structshttp.Handler InterfaceZusammenfassung - REST-API-Modelle14Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Method-MusterState-MusterComposite-MusterMiddleware als Decorator3Zeiger & Speicher
Zeiger-Grundlagen in GoZeiger auf StructsWertübergabe vs. ReferenzübergabeDie new() FunktionGarbage Collection in GoWiederholung – Linked List Builder6Polymorphismus in Go
Polymorphismus über InterfacesDuck Typing in GoInterface-ErfüllungsregelnPolymorphe CollectionsDependency InjectionZusammenfassung – Payment Processor9Nebenläufigkeit & OOP
Goroutines GrundlagenChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignZusammenfassung - Worker Pool