Strategy-Muster
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 90 von 107.
Das Strategie-Muster ermöglicht es dir, eine Familie von Algorithmen zu definieren, jeden einzelnen zu kapseln und sie zur Laufzeit austauschbar zu machen. Im Gegensatz zum Observer-Muster, bei dem mehrere Objekte auf ein Ereignis reagieren, kannst du mit Strategy ein einzelnes Objekt dynamisch zwischen verschiedenen Verhaltensweisen wechseln lassen.
In Go implementieren wir Strategy, indem wir ein Interface für den Algorithmus definieren und verschiedene Implementierungen in eine Kontextstruktur injizieren:
type PaymentStrategy interface {
Pay(amount float64) string
}
type CreditCard struct{}
func (c CreditCard) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f via Credit Card", amount)
}
type PayPal struct{}
func (p PayPal) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f via PayPal", amount)
}Die Kontextstruktur enthält eine Referenz auf die Strategieschnittstelle, sodass der Algorithmus ausgetauscht werden kann, ohne den Code des Kontexts zu ändern:
type Checkout struct {
strategy PaymentStrategy
}
func (c *Checkout) SetStrategy(s PaymentStrategy) {
c.strategy = s
}
func (c *Checkout) ProcessPayment(amount float64) string {
return c.strategy.Pay(amount)
}Jetzt kannst du die Zahlungsmethode zur Laufzeit ändern:
checkout := &Checkout{}
checkout.SetStrategy(CreditCard{})
fmt.Println(checkout.ProcessPayment(100.00)) // Bezahlt 100.00 via Kreditkarte
checkout.SetStrategy(PayPal{})
fmt.Println(checkout.ProcessPayment(50.00)) // Bezahlt 50.00 via PayPalStrategy ist ideal, wenn du mehrere Möglichkeiten hast, eine Operation auszuführen, und anhand von Benutzereingaben, der Konfiguration oder Laufzeitbedingungen eine davon auswählen musst. Häufige Anwendungsfälle sind Sortieralgorithmen, Komprimierungsmethoden und Validierungsregeln.
Aufgabe
EinfachErstellen wir ein Textformatierungssystem mithilfe des Strategy-Musters! Du erstellst verschiedene Formatierungsstrategien, die zur Laufzeit ausgetauscht werden können, sodass derselbe Textprozessor unterschiedliche Ausgabestile erzeugen kann, ohne seinen Kerncode zu ändern.
Du organisierst deinen Code über drei Dateien:
formatter.go: Definiere deine Strategie-Schnittstelle und konkrete Formatierungsstrategien.Erstelle eine
TextFormatter-Schnittstelle mit einer MethodeFormat(text string) string, die Text gemäß den Regeln der Strategie umwandelt.Implementiere drei Formatierungsstrategien:
UppercaseFormatter: wandelt den Text vollständig in Großbuchstaben umSnakeCaseFormatter: wandelt Leerzeichen in Unterstriche um und schreibt den Text in KleinbuchstabenTitleFormatter: schreibt den ersten Buchstaben jedes Wortes groß (Wörter werden durch Leerzeichen getrennt)
processor.go: Erstelle deine Kontextstruktur, die die Formatierungsstrategie verwendet.Erstelle eine
TextProcessor-Struktur, die eineTextFormatter-Strategie enthält. Füge diese Methoden hinzu:SetFormatter(f TextFormatter), um die aktive Formatierungsstrategie zu ändernProcess(text string) string, die den aktuellen Formatierer auf den Text anwendet
Erstelle außerdem einen
NewTextProcessor()-Konstruktor, der einen Prozessor ohne anfänglich gesetzten Formatierer zurückgibt.main.go: Zeige das Umschalten zwischen Strategien zur Laufzeit.Lies einen Text ein und anschließend die Anzahl der Formatierungsoperationen. Lies für jede Operation den Formattyp (
upper,snakeodertitle) ein, setze den entsprechenden Formatierer für deinen Prozessor, verarbeite den ursprünglichen Text und gib das Ergebnis aus.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Der zu formatierende Text
- Zeile 2: Anzahl der Formatierungsoperationen
- Folgende Zeilen: Formattyp für jede Operation
Zum Beispiel bei folgender Eingabe:
hello world today
3
upper
snake
titleDeine Ausgabe sollte folgendermaßen aussehen:
HELLO WORLD TODAY
hello_world_today
Hello World TodayUnd bei folgender Eingabe:
Go Programming Language
2
snake
upperDeine Ausgabe sollte folgendermaßen aussehen:
go_programming_language
GO PROGRAMMING LANGUAGEUnd bei folgender Eingabe:
design patterns are useful
1
titleDeine Ausgabe sollte folgendermaßen aussehen:
Design Patterns Are UsefulBeachte, wie derselbe TextProcessor völlig unterschiedliche Ausgaben erzeugt, indem einfach seine Formatierungsstrategie ausgetauscht wird. Der Prozessor weiß nicht, welcher konkrete Formatierer verwendet wird, und muss es auch nicht wissen. Er delegiert die Aufgabe einfach an die Strategie, die aktuell gesetzt ist!
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Lese den zu formatierenden Text
text, _ := reader.ReadString('\n')
text = strings.TrimSpace(text)
// Lese die Anzahl der Operationen
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// Erstelle einen neuen Textprozessor
processor := NewTextProcessor()
// Verarbeite jede Formatierungsoperation
for i := 0; i < count; i++ {
formatType, _ := reader.ReadString('\n')
formatType = strings.TrimSpace(formatType)
// TODO: Basierend auf formatType ("upper", "snake" oder "title"),
// setze den entsprechenden Formatter auf dem Prozessor
// und gib den verarbeiteten Text aus
}
}
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 Sammlung13Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterAbstraktes Factory-MusterObserver-MusterStrategy-Muster2Typen & 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 – Dateiparser3Zeiger & 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