State-Muster
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 95 von 107.
Das State-Pattern ermöglicht es einem Objekt, sein Verhalten zu ändern, wenn sich sein interner Zustand ändert, sodass es so aussieht, als ob das Objekt seine Klasse gewechselt hätte. Während die Template-Methode die Algorithmusschritte steuert, kapselt State zustandsspezifisches Verhalten in separate Objekte und delegiert an den aktuellen Zustand.
In Go definieren wir ein State-Interface und erstellen konkrete Zustände, die unterschiedliche Verhaltensweisen implementieren:
type State interface {
Handle(d *Document) string
}
type Document struct {
state State
}
func (d *Document) SetState(s State) {
d.state = s
}
func (d *Document) Publish() string {
return d.state.Handle(d)
}Jeder Zustand bestimmt, was passiert und welcher Zustand als Nächstes folgt:
type DraftState struct{}
func (s DraftState) Handle(d *Document) string {
d.SetState(ModerationState{})
return "Draft submitted for moderation"
}
type ModerationState struct{}
func (s ModerationState) Handle(d *Document) string {
d.SetState(PublishedState{})
return "Moderation approved, now published"
}
type PublishedState struct{}
func (s PublishedState) Handle(d *Document) string {
return "Already published"
}Derselbe Methodenaufruf liefert je nach aktuellem Zustand unterschiedliche Ergebnisse:
doc := &Document{state: DraftState{}}
fmt.Println(doc.Publish()) // Entwurf zur Moderation eingereicht
fmt.Println(doc.Publish()) // Moderation genehmigt, jetzt veröffentlicht
fmt.Println(doc.Publish()) // Bereits veröffentlichtState ist ideal für Objekte mit unterschiedlichen Betriebsmodi, wie z. B. Workflows zur Auftragsabwicklung, UI-Komponenten oder Verbindungshandler, bei denen das Verhalten vollständig vom aktuellen Zustand abhängt.
Aufgabe
EinfachLassen Sie uns ein Ticket-Support-System mit dem State-Pattern erstellen! Sie werden ein Support-Ticket erstellen, das verschiedene Phasen durchläuft – vom Öffnen über die Bearbeitung bis hin zur Lösung – wobei jeder Zustand bestimmt, welche Aktionen möglich sind und was als Nächstes passiert.
Sie werden Ihren Code in drei Dateien organisieren:
state.go: Definieren Sie Ihr State-Interface und die konkreten Zustände, die jede Phase im Lebenszyklus eines Tickets repräsentieren.Erstellen Sie ein
TicketState-Interface mit einer MethodeHandle(t *Ticket) string, die das Ticket verarbeitet und es potenziell in den nächsten Zustand überführt.Implementieren Sie drei Zustände:
OpenState— wenn verarbeitet, wechselt das Ticket zuInProgressStateund gibtTicket opened, assigning to support teamzurückInProgressState— wenn verarbeitet, wechselt es zuResolvedStateund gibtWorking on ticket, issue resolvedzurückResolvedState— wenn verarbeitet, bleibt es im gleichen Zustand und gibtTicket already resolvedzurück
ticket.go: Erstellen Sie Ihr Ticket-Struct, das den aktuellen Zustand hält und das Verhalten an diesen delegiert.Erstellen Sie ein
Ticket-Struct mit einemID-Feld (string) und einemstate-Feld (TicketState). Fügen Sie diese Methoden hinzu:SetState(s TicketState), um den aktuellen Zustand des Tickets zu ändernProcess() string, das an die Handle-Methode des aktuellen Zustands delegiert
Erstellen Sie einen
NewTicket(id string) *Ticket-Konstruktor, der ein Ticket zurückgibt, das imOpenStatebeginnt.main.go: Demonstrieren Sie, wie dieselbe Aktion basierend auf dem aktuellen Zustand des Tickets unterschiedliche Ergebnisse liefert.Lesen Sie eine Ticket-ID und die Anzahl der Male ein, die das Ticket verarbeitet werden soll. Erstellen Sie ein neues Ticket mit dieser ID und rufen Sie dann
Process()so oft wie angegeben auf, wobei jedes Ergebnis in einer separaten Zeile ausgegeben wird.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Ticket-ID
- Zeile 2: Anzahl der Male, die das Ticket verarbeitet werden soll
Zum Beispiel, gegeben:
TKT-001
3Ihre Ausgabe sollte sein:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolvedUnd gegeben:
TKT-500
5Ihre Ausgabe sollte sein:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved
Ticket already resolved
Ticket already resolvedUnd gegeben:
ISSUE-42
1Ihre Ausgabe sollte sein:
Ticket opened, assigning to support teamBeachten Sie, wie der Aufruf von Process() für dasselbe Ticket jedes Mal andere Ergebnisse liefert – das Verhalten des Tickets ändert sich, während es die Zustände durchläuft. Sobald es gelöst ist, bleibt es gelöst, egal wie oft Sie es verarbeiten. Das Ticket-Objekt scheint sein Verhalten zu ändern, aber in Wirklichkeit delegiert es an verschiedene State-Objekte!
Spickzettel
Das State-Pattern (Zustandsmuster) ermöglicht es einem Objekt, sein Verhalten zu ändern, wenn sich sein interner Zustand ändert. Das Objekt delegiert das Verhalten an separate Zustandsobjekte, die verschiedene Zustände repräsentieren.
Definieren Sie ein State-Interface:
type State interface {
Handle(d *Document) string
}Erstellen Sie ein Kontext-Objekt, das den aktuellen Zustand hält:
type Document struct {
state State
}
func (d *Document) SetState(s State) {
d.state = s
}
func (d *Document) Publish() string {
return d.state.Handle(d)
}Implementieren Sie konkrete Zustände, die zustandsspezifisches Verhalten kapseln:
type DraftState struct{}
func (s DraftState) Handle(d *Document) string {
d.SetState(ModerationState{})
return "Draft submitted for moderation"
}
type ModerationState struct{}
func (s ModerationState) Handle(d *Document) string {
d.SetState(PublishedState{})
return "Moderation approved, now published"
}
type PublishedState struct{}
func (s PublishedState) Handle(d *Document) string {
return "Already published"
}Jeder Zustand bestimmt, was passiert, und geht in den nächsten Zustand über:
doc := &Document{state: DraftState{}}
fmt.Println(doc.Publish()) // Draft submitted for moderation
fmt.Println(doc.Publish()) // Moderation approved, now published
fmt.Println(doc.Publish()) // Already publishedDas State-Pattern ist ideal für Objekte mit unterschiedlichen Betriebsmodi, bei denen das Verhalten vollständig vom aktuellen Zustand abhängt.
Probier es selbst
package main
import (
"fmt"
)
func main() {
// Eingabe lesen
var ticketID string
var numProcesses int
fmt.Scanln(&ticketID)
fmt.Scanln(&numProcesses)
// TODO: Erstelle ein neues Ticket mit der angegebenen ID
// TODO: Verarbeite das Ticket die angegebene Anzahl an Malen
// und gib jedes Ergebnis in einer separaten Zeile 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 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