2
3

More than 3 years have passed since last update.

Golangで、デザインパターン「State」を学ぶ

Posted at

GoFのデザインパターンをGolangで学習してみたいと思います。
今回は、Qiita記事: "Pythonで、デザインパターン「State」を学ぶ"で取り上げた、Pythonベースの”State”のサンプルアプリをGolangで実装し直してみました。

■ State(ステート・パターン)

Stateパターンとは、プログラミングで用いられる振る舞いに関する(英語版) デザインパターンの一種である。このパターンはオブジェクトの状態(state)を表現するために用いられる。ランタイムでそのタイプを部分的に変化させるオブジェクトを扱うクリーンな手段となる。

UML class and sequence diagram

W3sDesign_State_Design_Pattern_UML.jpg

UML class diagram

state.png
(以上、ウィキペディア(Wikipedia)より引用)

□ 備忘録

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インタフェースが、この役を努めます。

state/state.go
package state

import "fmt"

// State is interface
type State interface {
    handle(context *Context)
    getConcreateState() string
}

(2) ConcreteState(具体的な状態)の役

ConcreteState役は、具体的な個々の状態を表現するものです。
State役で定められたインタフェースを具体的に実装します。
サンプルプログラムでは、

  • ConcreteStateBooting構造体
  • ConcreteStateRun構造体
  • ConcreteStateShutDown構造体
  • ConcreteStateRestart構造体

が、この役を努めます。

state/state.go
type concreteState struct {
    state string
}

func (c *concreteState) getConcreateState() string {
    return c.state
}
state/state.go
// 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"))
}
state/state.go
// 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("*** パソコンは、動作中です")
}
state/state.go
// 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("*** パソコンは、停止しています")
}
state/state.go
// 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構造体が、この役を努めます。

state/context.go
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関数が、この役を努めます。

Main.go
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))
}

■ 参考URL

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3