Decorator-Muster
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 93 von 107.
Das Dekorierer-Muster ermöglicht es dir, Objekten dynamisch neues Verhalten hinzuzufügen, indem du sie in andere Objekte einhüllst. Während der Adapter eine Schnittstelle so verändert, dass sie zu einer anderen passt, behält der Dekorierer 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 dieselbe Schnittstelle implementieren:
type Notifier interface {
Send(message string) string
}
type BasicNotifier struct{}
func (b BasicNotifier) Send(message string) string {
return "Sending: " + message
}Decoratoren umschließen einen weiteren Notifier und fügen vor oder nach der Delegation an ihn Verhalten 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))
}Die Stärke des Decorator-Musters liegt darin, mehrere Wrapper zu stapeln, 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 du Objekten Verantwortlichkeiten hinzufügen musst, ohne ihren Code zu ändern, insbesondere wenn verschiedene Kombinationen von Funktionen benötigt werden. Es wird häufig für Protokollierungs-, Caching-, Authentifizierungs- und Komprimierungsschichten verwendet.
Aufgabe
EinfachErstellen wir ein Bestellsystem für ein Café mit dem Decorator-Muster! Du erstellst ein Basis-Kaffeegetränk und Decorators, die Extras wie Milch, Zucker und Schlagsahne hinzufügen. Jeder Decorator umschließt den vorherigen und baut sowohl die Beschreibung als auch den Preis auf: ein perfektes Beispiel aus der realen Welt dafür, wie Decorators Funktionalität schichten.
Du organisierst deinen Code über drei Dateien:
beverage.go: Definiere deine zentrale Schnittstelle und den grundlegenden Kaffeetyp.Erstelle eine
Beverage-Schnittstelle mit zwei Methoden:Description() string: gibt an, um welches Getränk es sich handeltCost() float64: gibt den Preis zurück
Implementiere eine
Coffee-Struktur als dein Basisgetränk. Ihre Beschreibung sollteCoffeesein und ihre Kosten sollten2.00betragen.decorators.go: Erstelle deine Decorator-Typen, die jedem Getränk Extras hinzufügen.Jeder Decorator umschließt ein
Beverageund implementiert dieselbe Schnittstelle. Erstelle drei Decorators:MilkDecorator: fügt der Beschreibung, Milkund den Kosten0.50hinzuSugarDecorator: fügt der Beschreibung, Sugarund den Kosten0.25hinzuWhipDecorator: fügt der Beschreibung, Whipund den Kosten0.75hinzu
Jeder Decorator sollte die Methoden des umschlossenen Getränks aufrufen und das Ergebnis erweitern.
main.go: Erstelle individuelle Getränke, indem du Decorators stapelst.Lies die Anzahl der hinzuzufügenden Extras ein. Lies dann für jedes Extra seinen Typ (
milk,sugaroderwhip) ein und umschließe dein aktuelles Getränk mit dem passenden Decorator.Gib nach dem Anwenden aller Extras die Beschreibung und die Kosten des fertigen Getränks in separaten Zeilen aus. Formatiere 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 deine Ausgabe so aussehen:
Coffee, Milk, Sugar
$2.75Und bei folgender Eingabe:
3
whip
milk
milksollte deine Ausgabe so aussehen:
Coffee, Whip, Milk, Milk
$3.75Und bei folgender Eingabe:
0sollte deine Ausgabe so aussehen:
Coffee
$2.00Beachte, wie jeder Decorator das vorherige Getränk umschließt und dabei sowohl die Beschreibung als auch die Kosten aufbaut. Du kannst dasselbe Extra mehrmals hinzufügen (zum Beispiel doppelte Milch), und die Reihenfolge der Decorators bestimmt die Reihenfolge in der Beschreibung. Der Basiskaffee weiß nichts über die Extras: Jeder Decorator erweitert einfach das, was er umschließt!
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 Extra-Typ, 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 OOP in Go
Externe DateienGo-Workspace & ModulePackages & ImportsExportierte vs. nicht exportierte NamenEinführung in OOP mit GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktorfunktionenRückblick – Einfacher Taschenrechner4Schnittstellen
Einführung in SchnittstellenImplizite ImplementierungSchnittstelle als VertragLeere Schnittstelle (any)TypzusicherungTypwechselZusammensetzung von SchnittstellenStringer- und Error-SchnittstellenRückblick – Formenrechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf PaketebeneGetter- und Setter-MethodenInformationsverbergung in GoRückblick – Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterTypbeschränkungenGenerische StrukturenWorkaround für generische MethodenZusammenfassung – Generische Sammlung2Typen & Structs im Detail
Grundlegende & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct-TagsAnonyme StructsVerschachtelte StructsNullwerte & StandardwerteRückblick – Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen der Struct-EinbettungMethoden-PromotionMehrere Structs einbettenEinbettung vs. AggregationVerbergen eingebetteter MethodenRückblick – Mitarbeiterhierarchie8Fehlerbehandlung & OOP
Das Error-InterfaceBenutzerdefinierte FehlertypenError-Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverRückblick – Dateiparser11Standardbibliothek & OOP
io.Reader & io.Writersort.Interfacefmt.Stringer-Interfaceencoding/json mit Structshttp.Handler-InterfaceRückblick – REST-API-Modelle14Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Methoden-MusterState-MusterComposite-MusterMiddleware als Decorator3Zeiger & Speicher
Grundlagen von Zeigern in GoZeiger auf StructsÜbergabe per Wert vs. ReferenzDie Funktion new()Garbage Collection in GoRückblick – Verkettete Liste erstellen6Polymorphismus in Go
Polymorphismus über InterfacesDuck-Typing in GoRegeln zur Interface-ErfüllungPolymorphe SammlungenDependency InjectionZusammenfassung – Zahlungsprozessor9Konkurrenz & OOP
Grundlagen der GoroutinesChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignRückblick – Worker PoolÜbe selbstständig: Online-Go-Compiler