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

GoFのデザインパターンをPythonで学習してみたいと思います。

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

## UML class and sequence diagram
![W3sDesign_State_Design_Pattern_UML.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/2e2b5e97-53c1-09b9-bcc2-c47602ffc501.jpeg)
## UML class diagram
![state.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/6bc61e44-7564-20ec-6cb9-1700314667da.png)
[(以上、ウィキペディア（Wikipedia）より引用)](https://en.wikipedia.org/wiki/State_pattern)

## □ 備忘録
`State`パターンでは、「状態」という物をクラスで表現するそうです。
状態に依存した振る舞いをここの`ConcreteState`役に分担させることが可能になります。
ただ、`State`パターンを使う場合、**状態遷移を誰が管理すべきか**という点には注意が必要です。
（状態遷移を`ConcreteState`役に任せてしまうとクラス間の依存関係を深めてしまいます。）

# ■ "State"のサンプルプログラム
実際に、Stateパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。ここでは、<font color="blue">"パソコン操作による起動状態の可視化"</font>をイメージしてください。

- パソコンを起動すると、動作状態は、"running"になる
- パソコンを停止すると、動作状態が、"shutdown"になる
- パソコンを再起動すると、動作状態が、"running"になる


なお、サンプルプログラムでは、**第一引数：最初のパソコン操作**、**第二引数：二度目のパソコン操作**を指定します。

## (事例1) パソコンを起動して、停止する
動作状態は、最初、パソコンに起動よって、"running"になって、その後、パソコンの停止によって、"shutdown"になります。

```console
$ python Main.py start stop
### パソコンを、[start]します
*** パソコンは、起動中です
### パソコンは、[running]の動作状態になりました

... sleep 5 second

### パソコンを、[stop]します
*** パソコンは、停止しています
### パソコンの動作状態は、[shutdown]になりました
```

## (事例2) パソコンを起動して、再起動する
動作状態は、最初、パソコンに起動よって、"running"になって、その後、パソコンの再起動によって、再び、"running"になります。

```console
$ python Main.py start restart
### パソコンを、[start]します
*** パソコンは、起動中です
### パソコンは、[running]の動作状態になりました

... sleep 5 second

### パソコンを、[restart]します
*** パソコンは、再起動をはじめます
*** パソコンは、起動中です
### パソコンの動作状態は、[running]になりました
```
以上で、想定どおり、サンプリプログラムが動作しました。

# ■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/State

- ディレクトリ構成

```console
.
├── Main.py
└── state
    ├── __init__.py
    ├── context.py
    └── state.py
```
## (1) State（状態）の役
`State`役は、状態を表すためのものです。状態ごとに異なる振る舞いをするインタフェースを定めます。
サンプルプログラムでは、`State`クラスが、この役を努めます。

```python:state/state.py
from abc import ABCMeta, abstractmethod

class State(metaclass=ABCMeta):
    @abstractmethod
    def handle(self):
        pass
```
## (2) ConcreteState（具体的な状態）の役
`ConcreteState`役は、具体的な個々の状態を表現するものです。
`State`役で定められたインタフェースを具体的に実装します。
サンプルプログラムでは、

- `ConcreteStateBooting`クラス
- `ConcreteStateRun`クラス
- `ConcreteStateShutDown`クラス
- `ConcreteStateRestart`クラス

が、この役を努めます。

```python:state/state.py
class ConcreteState(State):
    def __init__(self, state):
        self.state = state
    
    def getConcreateState(self):
        return self.state


class ConcreteStateBooting(ConcreteState):
    def __init__(self, state):
        super(ConcreteStateBooting, self).__init__(state)

    def handle(self, context):
        print("*** パソコンは、起動中です")
        context.setState(ConcreteStateRun("running"))


class ConcreteStateRun(ConcreteState):
    def __init__(self, state):
        super(ConcreteStateRun, self).__init__(state)

    def handle(self, context):
        print("*** パソコンは、動作中です")


class ConcreteStateShutDown(ConcreteState):
    def __init__(self, state):
        super(ConcreteStateShutDown, self).__init__(state)

    def handle(self, context):
        print("*** パソコンは、停止しています")


class ConcreteStateRestart(ConcreteState):
    def __init__(self, state):
        super(ConcreteStateRestart, self).__init__(state)

    def handle(self, context):
        print("*** パソコンは、再起動をはじめます")
        context.setState(ConcreteStateBooting("booting"))
        context.handle()
```
## (3) Context（状態、前後関係、文脈）の役
`Context`役は、現在の状態を表す`ConcreteState`役のオブジェクトを保持します。
サンプルプログラムでは、`Context`クラスが、この役を努めます。

```python:state/context.py
class Context(object):
    def __init__(self, stateObj):
        self.state = stateObj

    def setState(self, obj):
        self.state = obj
    
    def handle(self):
        self.state.handle(self)

    def getState(self):
        return self.state.getConcreateState()
```
## (4) Client（依頼人）の役
サンプルプログラムでは、`startMain`メソッドが、この役を努めます。

```python:Main.py
import sys
import time
from state.context import Context
from state.state import ConcreteStateBooting, ConcreteStateRun, ConcreteStateShutDown, ConcreteStateRestart


def setConcreteState(operation):
    if operation == "start":
        return ConcreteStateBooting("booting")
    elif operation == "stop":
        return ConcreteStateShutDown("shutdown")
    elif operation == "restart":
        return ConcreteStateRestart("restart")

def startMain(initial_operation, change_operation):
    obj = Context(setConcreteState(initial_operation))
    print("### パソコンを、[{0}]します".format(initial_operation))
    obj.handle()
    print("### パソコンは、[{0}]の動作状態になりました".format(obj.getState()))
    print("")

    print("... sleep 5 second")
    print("")
    time.sleep(5)

    obj.setState(setConcreteState(change_operation))
    print("### パソコンを、[{0}]します".format(change_operation))
    obj.handle()
    print("### パソコンの動作状態は、[{0}]になりました".format(obj.getState()))


if __name__ == "__main__":
    startMain(sys.argv[1], sys.argv[2])
```

# ■ 参考URL
- [Pythonにおけるデザインパターン ~Pyデザパタ~ / Stateパターン](https://pydp.info/GoF_dp/behavior/17_Mediator/)
- [State Design Pattern In Python](http://coursegalaxy.com/designpatterns/state/state_python1.htm)