GoFのデザインパターンをPythonで学習してみたいと思います。
■ State(ステート・パターン)
Stateパターンとは、プログラミングで用いられる振る舞いに関する(英語版) デザインパターンの一種である。このパターンはオブジェクトの状態(state)を表現するために用いられる。ランタイムでそのタイプを部分的に変化させるオブジェクトを扱うクリーンな手段となる。
UML class and sequence diagram
UML class diagram
□ 備忘録
State
パターンでは、「状態」という物をクラスで表現するそうです。
状態に依存した振る舞いをここのConcreteState
役に分担させることが可能になります。
ただ、State
パターンを使う場合、状態遷移を誰が管理すべきかという点には注意が必要です。
(状態遷移をConcreteState
役に任せてしまうとクラス間の依存関係を深めてしまいます。)
■ "State"のサンプルプログラム
実際に、Stateパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。ここでは、"パソコン操作による起動状態の可視化"をイメージしてください。
- パソコンを起動すると、動作状態は、"running"になる
- パソコンを停止すると、動作状態が、"shutdown"になる
- パソコンを再起動すると、動作状態が、"running"になる
なお、サンプルプログラムでは、第一引数:最初のパソコン操作、第二引数:二度目のパソコン操作を指定します。
(事例1) パソコンを起動して、停止する
動作状態は、最初、パソコンに起動よって、"running"になって、その後、パソコンの停止によって、"shutdown"になります。
$ python Main.py start stop
### パソコンを、[start]します
*** パソコンは、起動中です
### パソコンは、[running]の動作状態になりました
... sleep 5 second
### パソコンを、[stop]します
*** パソコンは、停止しています
### パソコンの動作状態は、[shutdown]になりました
(事例2) パソコンを起動して、再起動する
動作状態は、最初、パソコンに起動よって、"running"になって、その後、パソコンの再起動によって、再び、"running"になります。
$ python Main.py start restart
### パソコンを、[start]します
*** パソコンは、起動中です
### パソコンは、[running]の動作状態になりました
... sleep 5 second
### パソコンを、[restart]します
*** パソコンは、再起動をはじめます
*** パソコンは、起動中です
### パソコンの動作状態は、[running]になりました
以上で、想定どおり、サンプリプログラムが動作しました。
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/State
- ディレクトリ構成
.
├── Main.py
└── state
├── __init__.py
├── context.py
└── state.py
(1) State(状態)の役
State
役は、状態を表すためのものです。状態ごとに異なる振る舞いをするインタフェースを定めます。
サンプルプログラムでは、State
クラスが、この役を努めます。
from abc import ABCMeta, abstractmethod
class State(metaclass=ABCMeta):
@abstractmethod
def handle(self):
pass
(2) ConcreteState(具体的な状態)の役
ConcreteState
役は、具体的な個々の状態を表現するものです。
State
役で定められたインタフェースを具体的に実装します。
サンプルプログラムでは、
-
ConcreteStateBooting
クラス -
ConcreteStateRun
クラス -
ConcreteStateShutDown
クラス -
ConcreteStateRestart
クラス
が、この役を努めます。
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
クラスが、この役を努めます。
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
メソッドが、この役を努めます。
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])