Stateパターン
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 95/107。
Stateパターンを使用すると、オブジェクトの内部状態が変化したときにその振る舞いを変更でき、まるでオブジェクトのクラスが変わったかのように見せることができます。Template Methodがアルゴリズムの手順を制御するのに対し、Stateは状態ごとの振る舞いを個別のオブジェクトにカプセル化し、current stateに委譲します。
Goでは、stateインターフェースを定義し、異なるbehaviorをImplementする具体的なstateを作成します。
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)
}各状態は、何が起こり、次にどの状態になるかを決定します:
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"
}同じメソッド呼び出しでも、current state に応じて異なる結果が生成されます。
doc := &Document{state: DraftState{}}
fmt.Println(doc.Publish()) // 下書きがモデレーションに提出されました
fmt.Println(doc.Publish()) // モデレーションが承認され、公開されました
fmt.Println(doc.Publish()) // 既に公開されていますStateは、注文処理ワークフロー、UIコンポーネント、接続ハンドラーなど、behaviorがcurrent stateに完全に依存する、明確に異なる動作モードを持つオブジェクトに最適です。
チャレンジ
簡単State パターンを使ってチケットサポートシステムを構築しましょう!チケットが開かれた状態から、対応中の状態、解決済みの状態へとさまざまな段階を移行し、各状態によって可能なアクションと次に起こることが決まるサポートチケットを作成します。
コードを次の3つのファイルに分けて整理します。
state.go: チケットのライフサイクルの各段階を表す state interface と具体的な状態を Define します。TicketStateinterface を作成し、チケットを処理して、次の状態へ遷移させる可能性のあるメソッドHandle(t *Ticket) stringを定義します。次の3つの状態を Implement します。
OpenState: handled されると、チケットをInProgressStateに遷移させ、Ticket opened, assigning to support teamを返しますInProgressState: handled されると、ResolvedStateに遷移し、Working on ticket, issue resolvedを返しますResolvedState: handled されると、同じ状態にとどまり、Ticket already resolvedを返します
ticket.go: current state を保持し、その behavior を委譲するチケット構造体を作成します。IDfield(string)とstatefield(TicketState)を持つTicketstruct を構築します。次のメソッドを追加します。- チケットの current state を変更する
SetState(s TicketState) - current state の Handle method に処理を委譲する
Process() string
OpenStateで開始するチケットを returns するNewTicket(id string) *Ticketconstructor を作成します。- チケットの current state を変更する
main.go: チケットの current state に応じて、同じアクションが異なる結果を生成することを示します。チケット ID と、チケットを Process する回数を Read します。その ID で新しいチケットを作成し、指定された回数だけ
Process()を呼び出して、各 result を別々の行に出力します。
次の入力が提供されます。
- 1行目: チケット ID
- 2行目: チケットを Process する回数
たとえば、次の入力が given された場合:
TKT-001
3出力は次のようになります。
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolvedまた、次の入力が given された場合:
TKT-500
5出力は次のようになります。
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved
Ticket already resolved
Ticket already resolvedまた、次の入力が given された場合:
ISSUE-42
1出力は次のようになります。
Ticket opened, assigning to support team同じチケットに対して Process() を呼び出すと、毎回異なる result が生成されることに注目してください。チケットが各 state を移行するにつれて、その behavior が changes します。いったん resolved になると、何度 Process しても resolved のままです。チケットオブジェクト自体が behavior を changes しているように見えますが、実際には異なる state オブジェクトに delegates しています。
自分で試してみよう
package main
import (
"fmt"
)
func main() {
// 入力を読み取る
var ticketID string
var numProcesses int
fmt.Scanln(&ticketID)
fmt.Scanln(&numProcesses)
// TODO: 指定されたIDで新しいチケットを作成する
// TODO: チケットを指定された回数処理する
// そして各結果を別々の行に出力する
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー11標準ライブラリとOOP
io.Reader と io.Writersort.Interfacefmt.Stringer インターフェースencoding/json と構造体http.Handler インターフェースまとめ:REST API モデル14デザインパターン パート2
CommandパターンAdapterパターンDecoratorパターンTemplate MethodパターンStateパターンCompositeパターンDecoratorとしてのMiddleware自分で練習してみよう: Goオンラインコンパイラ