Strategy-Muster
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 90 von 107.
Das Strategy-Pattern ermöglicht es Ihnen, 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, erlaubt es Strategy einem einzelnen Objekt, dynamisch zwischen verschiedenen Verhaltensweisen zu wechseln.
In Go implementieren wir Strategy, indem wir ein Interface für den Algorithmus definieren und verschiedene Implementierungen in ein Context-Struct 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 Context-Struktur hält eine Referenz auf das Strategy-Interface, was es ermöglicht, den Algorithmus auszutauschen, ohne den Code des Contexts 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 können Sie die Zahlungsmethode zur Laufzeit ändern:
checkout := &Checkout{}
checkout.SetStrategy(CreditCard{})
fmt.Println(checkout.ProcessPayment(100.00)) // 100.00 via Kreditkarte bezahlt
checkout.SetStrategy(PayPal{})
fmt.Println(checkout.ProcessPayment(50.00)) // 50.00 via PayPal bezahltDas Strategie-Muster (Strategy) ist ideal, wenn Sie mehrere Möglichkeiten haben, eine Operation auszuführen, und eine davon basierend auf Benutzereingaben, Konfigurationen oder Laufzeitbedingungen auswählen müssen. Häufige Anwendungsfälle sind Sortieralgorithmen, Kompressionsmethoden und Validierungsregeln.
Aufgabe
EinfachLassen Sie uns ein Textformatierungssystem mit dem Strategy-Pattern erstellen! Sie werden verschiedene Formatierungsstrategien entwickeln, die zur Laufzeit ausgetauscht werden können. Dies ermöglicht es demselben Textprozessor, verschiedene Ausgabestile zu erzeugen, ohne seinen Kerncode zu ändern.
Sie werden Ihren Code auf drei Dateien verteilen:
formatter.go: Definieren Sie Ihr Strategy-Interface und die konkreten Formatierungsstrategien.Erstellen Sie ein
TextFormatter-Interface mit einer MethodeFormat(text string) string, die Text gemäß den Regeln der Strategie transformiert.Implementieren Sie drei Formatierungsstrategien:
UppercaseFormatter— wandelt Text in Großbuchstaben umSnakeCaseFormatter— ersetzt Leerzeichen durch Unterstriche und wandelt Text in Kleinbuchstaben umTitleFormatter— schreibt den ersten Buchstaben jedes Wortes groß (Wörter sind durch Leerzeichen getrennt)
processor.go: Erstellen Sie Ihr Kontext-Struct, das die Formatierungsstrategie verwendet.Erstellen Sie ein
TextProcessor-Struct, das eineTextFormatter-Strategie hält. Fügen Sie diese Methoden hinzu:SetFormatter(f TextFormatter), um die aktive Formatierungsstrategie zu ändernProcess(text string) string, die den aktuellen Formatter auf den Text anwendet
Erstellen Sie außerdem einen
NewTextProcessor()-Konstruktor, der einen Prozessor ohne initial gesetzten Formatter zurückgibt.main.go: Demonstrieren Sie das Wechseln von Strategien zur Laufzeit.Lesen Sie einen Text ein und lesen Sie dann die Anzahl der Formatierungsoperationen. Lesen Sie für jede Operation den Formattyp (
upper,snakeodertitle), setzen Sie den entsprechenden Formatter in Ihrem Prozessor, verarbeiten Sie den ursprünglichen Text und geben Sie 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, gegeben:
hello world today
3
upper
snake
titleIhre Ausgabe sollte sein:
HELLO WORLD TODAY
hello_world_today
Hello World TodayUnd gegeben:
Go Programming Language
2
snake
upperIhre Ausgabe sollte sein:
go_programming_language
GO PROGRAMMING LANGUAGEUnd gegeben:
design patterns are useful
1
titleIhre Ausgabe sollte sein:
Design Patterns Are UsefulBeachten Sie, wie derselbe TextProcessor völlig unterschiedliche Ausgaben erzeugt, nur indem seine Formatter-Strategie ausgetauscht wird. Der Prozessor weiß nicht und es ist ihm egal, welchen spezifischen Formatter er verwendet – er delegiert einfach an die Strategie, die gerade gesetzt ist!
Spickzettel
Das Strategy-Muster definiert eine Familie von Algorithmen, kapselt jeden einzelnen und macht sie zur Laufzeit austauschbar. Es ermöglicht einem einzelnen Objekt, dynamisch zwischen verschiedenen Verhaltensweisen zu wechseln.
Definieren Sie ein Interface für den Algorithmus:
type PaymentStrategy interface {
Pay(amount float64) string
}Implementieren Sie konkrete Strategien:
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)
}Erstellen Sie ein Context-Struct, das eine Referenz auf das Strategy-Interface hält:
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)
}Strategien zur Laufzeit ändern:
checkout := &Checkout{}
checkout.SetStrategy(CreditCard{})
fmt.Println(checkout.ProcessPayment(100.00)) // Paid 100.00 via Credit Card
checkout.SetStrategy(PayPal{})
fmt.Println(checkout.ProcessPayment(50.00)) // Paid 50.00 via PayPalDas Strategy-Muster ist ideal, wenn Sie mehrere Möglichkeiten haben, eine Operation auszuführen, und eine davon basierend auf Benutzereingaben, Konfigurationen oder Laufzeitbedingungen auswählen müssen.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Zu formatierenden Text lesen
text, _ := reader.ReadString('\n')
text = strings.TrimSpace(text)
// Anzahl der Operationen lesen
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// Einen neuen Textprozessor erstellen
processor := NewTextProcessor()
// Jede Formatierungsoperation verarbeiten
for i := 0; i < count; i++ {
formatType, _ := reader.ReadString('\n')
formatType = strings.TrimSpace(formatType)
// TODO: Basierend auf formatType ("upper", "snake" oder "title"),
// den entsprechenden Formatter auf dem Prozessor setzen
// und den verarbeiteten Text ausgeben
}
}
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 Collection13Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterAbstract-Factory-MusterObserver-MusterStrategy-Muster2Vertiefung: 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-Parser3Zeiger & 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