GoFのデザインパターンをGolangで学習してみたいと思います。
今回は、Qiita記事: "Pythonで、デザインパターン「State」を学ぶ"で取り上げた、Pythonベースの”State”のサンプルアプリをGolangで実装し直してみました。
■ State(ステート・パターン)
Stateパターンとは、プログラミングで用いられる振る舞いに関する(英語版) デザインパターンの一種である。このパターンはオブジェクトの状態(state)を表現するために用いられる。ランタイムでそのタイプを部分的に変化させるオブジェクトを扱うクリーンな手段となる。
UML class and sequence diagram
UML class diagram
□ 備忘録
State
パターンでは、「状態」という物をクラスで表現するそうです。
状態に依存した振る舞いをここのConcreteState
役に分担させることが可能になります。
ただ、State
パターンを使う場合、状態遷移を誰が管理すべきかという点には注意が必要です。
(状態遷移をConcreteState
役に任せてしまうとクラス間の依存関係を深めてしまいます。)
■ "State"のサンプルプログラム
実際に、Stateパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。ここでは、"パソコン操作による起動状態の可視化"をイメージしてください。
- パソコンを起動すると、動作状態は、"running"になる
- パソコンを停止すると、動作状態が、"shutdown"になる
- パソコンを再起動すると、動作状態が、"running"になる
なお、サンプルプログラムでは、第一引数:最初のパソコン操作、第二引数:二度目のパソコン操作を指定します。
(事例1) パソコンを起動して、停止する
動作状態は、最初、パソコンに起動よって、"running"になって、その後、パソコンの停止によって、"shutdown"になります。
$ go run Main.go start stop
### パソコンを、[start]します
*** パソコンは、起動中です
### パソコンは、[running]の動作状態になりました
... sleep 5 second
### パソコンを、[stop]します
*** パソコンは、停止しています
### パソコンの動作状態は、[shutdown]になりました
(事例2) パソコンを起動して、再起動する
動作状態は、最初、パソコンに起動よって、"running"になって、その後、パソコンの再起動によって、再び、"running"になります。
$ go run Main.go start restart
### パソコンを、[start]します
*** パソコンは、起動中です
### パソコンは、[running]の動作状態になりました
... sleep 5 second
### パソコンを、[restart]します
*** パソコンは、再起動をはじめます
*** パソコンは、起動中です
### パソコンの動作状態は、[running]になりました
以上で、想定どおり、サンプリプログラムが動作しました。
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/State
- ディレクトリ構成
.
├── Main.go
└── state
├── context.go
└── state.go
(1) State(状態)の役
State
役は、状態を表すためのものです。状態ごとに異なる振る舞いをするインタフェースを定めます。
サンプルプログラムでは、State
インタフェースが、この役を努めます。
package state
import "fmt"
// State is interface
type State interface {
handle(context *Context)
getConcreateState() string
}
(2) ConcreteState(具体的な状態)の役
ConcreteState
役は、具体的な個々の状態を表現するものです。
State
役で定められたインタフェースを具体的に実装します。
サンプルプログラムでは、
-
ConcreteStateBooting
構造体 -
ConcreteStateRun
構造体 -
ConcreteStateShutDown
構造体 -
ConcreteStateRestart
構造体
が、この役を努めます。
type concreteState struct {
state string
}
func (c *concreteState) getConcreateState() string {
return c.state
}
// ConcreteStateBooting is struct
type ConcreteStateBooting struct {
*concreteState
}
// NewConcreteStateBooting func for initializing ConcreteStateBooting
func NewConcreteStateBooting(state string) *ConcreteStateBooting {
return &ConcreteStateBooting{
concreteState: &concreteState{
state: state,
},
}
}
func (c *ConcreteStateBooting) handle(context *Context) {
fmt.Println("*** パソコンは、起動中です")
context.SetState(NewConcreteStateRun("running"))
}
// ConcreteStateRun is struct
type ConcreteStateRun struct {
*concreteState
}
// NewConcreteStateRun func for initializing ConcreteStateRun
func NewConcreteStateRun(state string) *ConcreteStateRun {
return &ConcreteStateRun{
concreteState: &concreteState{
state: state,
},
}
}
func (c *ConcreteStateRun) handle(context *Context) {
fmt.Println("*** パソコンは、動作中です")
}
// ConcreteStateShutDown is struct
type ConcreteStateShutDown struct {
*concreteState
}
// NewConcreteStateShutDown func for initializing ConcreteStateShutDown
func NewConcreteStateShutDown(state string) *ConcreteStateShutDown {
return &ConcreteStateShutDown{
concreteState: &concreteState{
state: state,
},
}
}
func (c *ConcreteStateShutDown) handle(context *Context) {
fmt.Println("*** パソコンは、停止しています")
}
// ConcreteStateRestart is struct
type ConcreteStateRestart struct {
*concreteState
}
// NewConcreteStateRestart func for initializing ConcreteStateRestart
func NewConcreteStateRestart(state string) *ConcreteStateRestart {
return &ConcreteStateRestart{
concreteState: &concreteState{
state: state,
},
}
}
func (c *ConcreteStateRestart) handle(context *Context) {
fmt.Println("*** パソコンは、再起動をはじめます")
context.SetState(NewConcreteStateBooting("booting"))
context.Handle()
}
(3) Context(状態、前後関係、文脈)の役
Context
役は、現在の状態を表すConcreteState
役のオブジェクトを保持します。
サンプルプログラムでは、Context
構造体が、この役を努めます。
package state
// Context is struct
type Context struct {
state State
}
// NewContext func for initializing Context
func NewContext(stateObj State) *Context {
return &Context{
state: stateObj,
}
}
// SetState func for change state
func (c *Context) SetState(obj State) {
c.state = obj
}
// Handle func for handling state
func (c *Context) Handle() {
c.state.handle(c)
}
// GetState func for fetching state
func (c *Context) GetState() string {
return c.state.getConcreateState()
}
(4) Client(依頼人)の役
サンプルプログラムでは、startMain
関数が、この役を努めます。
package main
import (
"flag"
"fmt"
"time"
"./state"
)
func setConcreteState(operation string) state.State {
var stateObj state.State
if operation == "start" {
stateObj = state.NewConcreteStateBooting("booting")
} else if operation == "stop" {
stateObj = state.NewConcreteStateShutDown("shutdown")
} else if operation == "restart" {
stateObj = state.NewConcreteStateRestart("restart")
}
return stateObj
}
func startMain(initialOperation, changeOperation string) {
obj := state.NewContext(setConcreteState(initialOperation))
fmt.Printf("### パソコンを、[%s]します\n", initialOperation)
obj.Handle()
fmt.Printf("### パソコンは、[%s]の動作状態になりました\n", obj.GetState())
fmt.Println("")
fmt.Println("... sleep 5 second")
fmt.Println("")
time.Sleep(time.Second * 5)
obj.SetState(setConcreteState(changeOperation))
fmt.Printf("### パソコンを、[%s]します\n", changeOperation)
obj.Handle()
fmt.Printf("### パソコンの動作状態は、[%s]になりました\n", obj.GetState())
}
func main() {
flag.Parse()
startMain(flag.Arg(0), flag.Arg(1))
}