Menu
Coddy logo textTech

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öffentlicht

State 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.

challenge icon

Aufgabe

Einfach

Lassen 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 Methode Handle(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 zu InProgressState und gibt Ticket opened, assigning to support team zurück
    • InProgressState — wenn verarbeitet, wechselt es zu ResolvedState und gibt Working on ticket, issue resolved zurück
    • ResolvedState — wenn verarbeitet, bleibt es im gleichen Zustand und gibt Ticket already resolved zurü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 einem ID-Feld (string) und einem state-Feld (TicketState). Fügen Sie diese Methoden hinzu:

    • SetState(s TicketState), um den aktuellen Zustand des Tickets zu ändern
    • Process() string, das an die Handle-Methode des aktuellen Zustands delegiert

    Erstellen Sie einen NewTicket(id string) *Ticket-Konstruktor, der ein Ticket zurückgibt, das im OpenState beginnt.

  • 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
3

Ihre Ausgabe sollte sein:

Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved

Und gegeben:

TKT-500
5

Ihre Ausgabe sollte sein:

Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved
Ticket already resolved
Ticket already resolved

Und gegeben:

ISSUE-42
1

Ihre Ausgabe sollte sein:

Ticket opened, assigning to support team

Beachten 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 published

Das 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
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung