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ż opublikowanyWzorzec 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.
Wyzwanie
ŁatwyZbudujmy 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
TicketStatez 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 stanuInProgressStatei zwracaTicket opened, assigning to support teamInProgressState: po obsłużeniu przenosi zgłoszenie do stanuResolvedStatei zwracaWorking on ticket, issue resolvedResolvedState: po obsłużeniu pozostaje w tym samym stanie i zwracaTicket already resolved
ticket.go: Utwórz strukturę zgłoszenia, która przechowuje bieżący stan i deleguje do niego zachowanie.Utwórz strukturę
Ticketz polemIDtypu string i polemstatetypu TicketState. Dodaj następujące metody:SetState(s TicketState), aby zmienić bieżący stan zgłoszeniaProcess() 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 stanieOpenState.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
3Wynik powinien być następujący:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolvedA dla danych:
TKT-500
5Wynik powinien być następujący:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved
Ticket already resolved
Ticket already resolvedA dla danych:
ISSUE-42
1Wynik powinien być następujący:
Ticket opened, assigning to support teamZwróć 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
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy OOP w Go
Pliki zewnętrzneObszar roboczy i moduły GoPakiety i importyNazwy eksportowane i nieeksportowaneWprowadzenie do OOP w GoStruktury jako klasyDefiniowanie metod dla strukturOdbiorniki wskaźnikowe i wartościoweInicjalizacja strukturFunkcje konstruktoraPodsumowanie – prosty kalkulator4Interfejsy
Wprowadzenie do interfejsówImplementacja niejawnaInterfejs jako kontraktPusty interfejs (any)Asercja typuPrzełącznik typówKompozycja interfejsówInterfejsy Stringer i ErrorPowtórka — kalkulator figur7Hermetyzacja
Pola eksportowane i nieeksportowaneHermetyzacja na poziomie pakietuMetody pobierające i ustawiająceUkrywanie informacji w GoPowtórka – dane studentów10Typy generyczne (Go 1.18+)
Wprowadzenie do typów generycznychParametry typówOgraniczenia typówStruktury generyczneObejście ograniczeń metod generycznychPodsumowanie — generyczna kolekcja2Typy i struktury w praktyce
Typy proste i złożoneDefiniowanie własnych typówTagi pól strukturStruktury anonimoweStruktury zagnieżdżoneWartości zerowe i domyślnePodsumowanie – książka kontaktów5Kompozycja zamiast dziedziczenia
Dlaczego Go nie ma dziedziczeniaPodstawy osadzania strukturPromowanie metodOsadzanie wielu strukturOsadzanie a agregacjaPrzesłanianie osadzonych metodPodsumowanie — hierarchia pracowników8Obsługa błędów i programowanie obiektowe
Interfejs błęduWłasne typy błędówOpakowywanie błędów (fmt.Errorf)Błędy wartowniczeerrors.Is() i errors.As()Panic, Defer i RecoverPodsumowanie — parser plików11Biblioteka standardowa i OOP
io.Reader i io.Writersort.InterfaceInterfejs fmt.Stringerencoding/json ze strukturamiInterfejs http.HandlerPowtórzenie – modele REST API14Wzorce projektowe, część 2
Wzorzec poleceniaWzorzec adapteraWzorzec dekoratoraWzorzec metody szablonowejWzorzec stanuWzorzec kompozytuMiddleware jako dekorator3Wskaźniki i pamięć
Podstawy wskaźników w GoWskaźniki do strukturPrzekazywanie przez wartość a przez referencjęFunkcja new()Odśmiecanie pamięci w GoPowtórka — kreator listy wiązanej6Polimorfizm w Go
Polimorfizm za pomocą interfejsówDuck typing w GoZasady spełniania interfejsówKolekcje polimorficzneWstrzykiwanie zależnościPowtórka — procesor płatności9Współbieżność i OOP
Podstawy goroutinesKanały i komunikacjaKanały buforowane i niebuforowaneInstrukcja selectsync.Mutex i sync.RWMutexsync.WaitGroupProjektowanie struktur bezpiecznych wątkowoPowtórka – pula workerów12Zaawansowane zagadnienia programowania obiektowego
Wzorzec opcji funkcyjnychWzorzec Builder w GoŁańcuchowe wywoływanie metodAliasy typów a definicje typówPodstawy refleksjiPrzegląd generowania koduPoćwicz samodzielnie: Kompilator Go online