Menu
Coddy logo textTech

Wzorzec stanu

Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 95 z 107.

Wzorzec State pozwala obiektowi zmieniać zachowanie, gdy zmienia się jego stan wewnętrzny, przez co sprawia wrażenie, jakby obiekt zmienił swoją klasę. Podczas gdy wzorzec Template Method kontroluje kroki algorytmu, wzorzec State enkapsuluje zachowanie specyficzne dla danego stanu w osobnych obiektach i deleguje zadania do aktualnego stanu.

W Go definiujemy interfejs stanu i tworzymy konkretne stany implementujące różne zachowania:

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)
}

Każdy stan określa, co się dzieje i jaki stan następuje po nim:

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"
}

To samo wywołanie metody daje różne wyniki w zależności od bieżącego stanu:

doc := &Document{state: DraftState{}}

fmt.Println(doc.Publish())  // Wersja robocza przekazana do moderacji
fmt.Println(doc.Publish())  // Moderacja zatwierdzona, teraz opublikowany
fmt.Println(doc.Publish())  // Już opublikowany

Wzorzec Stan idealnie sprawdza się w przypadku obiektów o wyraźnie odrębnych trybach działania, takich jak przepływy pracy związane z przetwarzaniem zamówień, komponenty interfejsu użytkownika czy procedury obsługi połączeń, w których zachowanie zależy całkowicie od bieżącego stanu.

challenge icon

Wyzwanie

Łatwy

Zbudujmy system obsługi zgłoszeń za pomocą wzorca State! Utworzysz zgłoszenie, które przechodzi przez różne etapy: od otwarcia, przez obsługę, aż po rozwiązanie. Każdy stan określa, jakie działania są możliwe i co stanie się dalej.

Podzielisz swój kod na trzy pliki:

  • state.go: Zdefiniuj interfejs stanu oraz konkretne stany reprezentujące poszczególne etapy cyklu życia zgłoszenia.

    Utwórz interfejs TicketState z metodą Handle(t *Ticket) string, która obsługuje zgłoszenie i może przenieść je do następnego stanu.

    Zaimplementuj trzy stany:

    • OpenState: po obsłużeniu przenosi zgłoszenie do stanu InProgressState i zwraca Ticket opened, assigning to support team
    • InProgressState: po obsłużeniu przenosi zgłoszenie do stanu ResolvedState i zwraca Working on ticket, issue resolved
    • ResolvedState: po obsłużeniu pozostaje w tym samym stanie i zwraca Ticket already resolved
  • ticket.go: Utwórz strukturę zgłoszenia, która przechowuje bieżący stan i deleguje do niego zachowanie.

    Utwórz strukturę Ticket z polem ID typu string i polem state typu TicketState. Dodaj następujące metody:

    • SetState(s TicketState), aby zmienić bieżący stan zgłoszenia
    • Process() string, która deleguje obsługę do metody Handle bieżącego stanu

    Utwórz konstruktor NewTicket(id string) *Ticket, który zwraca zgłoszenie rozpoczynające w stanie OpenState.

  • main.go: Pokaż, jak ta sama czynność daje różne wyniki w zależności od bieżącego stanu zgłoszenia.

    Wczytaj identyfikator zgłoszenia oraz liczbę razy, jaką ma zostać obsłużone. Utwórz nowe zgłoszenie z tym identyfikatorem, a następnie wywołaj Process() określoną liczbę razy, wypisując każdy wynik w osobnym wierszu.

Dane wejściowe będą miały następującą postać:

  • Wiersz 1: identyfikator zgłoszenia
  • Wiersz 2: liczba razy, jaką należy obsłużyć zgłoszenie

Na przykład, dla danych:

TKT-001
3

Wynik powinien być następujący:

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

A dla danych:

TKT-500
5

Wynik powinien być następujący:

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

A dla danych:

ISSUE-42
1

Wynik powinien być następujący:

Ticket opened, assigning to support team

Zwróć uwagę, że wywołanie Process() na tym samym zgłoszeniu za każdym razem daje inny wynik: zachowanie zgłoszenia zmienia się w miarę przechodzenia przez kolejne stany. Po rozwiązaniu zgłoszenie pozostaje rozwiązane, niezależnie od tego, ile razy je obsłużysz. Wygląda na to, że obiekt zgłoszenia zmienia swoje zachowanie, ale w rzeczywistości deleguje je do różnych obiektów stanów!

Spróbuj swoich sił

package main

import (
	"fmt"
)

func main() {
	// Wczytaj dane wejściowe
	var ticketID string
	var numProcesses int
	fmt.Scanln(&ticketID)
	fmt.Scanln(&numProcesses)

	// TODO: Utwórz nowy bilet o podanym ID

	// TODO: Przetwórz bilet określoną liczbę razy
	// i wypisz każdy wynik w osobnym wierszu
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online