前回 (状態とふるまいを持つモデルを実装する)では、状態とふるまいを持つプリミティブなモデル Switch
を実装した。今回は Switch
に状態を追加して拡張を試みる。
ストップウォッチのような、三つの状態、待機状態WAIT
, 計測状態MEASURE
, 停止状態PAUSE
があるモデルを考える。ストップウォッチには、二つのボタン start_stop()
, reset()
があり、以下のような状態とふるまいを想定する1。
- 待機状態
WAIT
でボタンstart_stop()
が押下されると、計測状態MEASURE
になる。 - 計測状態
MEASURE
でボタンstart_stop()
が押下されると、停止状態PAUSE
になる。 - 停止状態
PAUSE
でボタンstart_stop()
が押下されると再び、計測状態MEASURE
になる。 - また、停止状態
PAUSE
でボタンreset()
が押下されると、待機状態WAIT
になる。
このモデルは以下のように状態遷移する。
watch = StopWatch()
assert watch.state == StopWatch.WAIT
watch.start_stop()
assert watch.state == StopWatch.MEASURE
watch.start_stop()
assert watch.state == StopWatch.PAUSE
watch.start_stop()
assert watch.state == StopWatch.MEASURE
watch.reset() # nothing to happen
assert watch.state == StopWatch.MEASURE
watch.start_stop()
assert watch.state == StopWatch.PAUSE
watch.reset()
assert watch.state == StopWatch.WAIT
まずは、ふるまいが状態に応じて変わるように実装する。
from enum import auto
class StopWatch:
WAIT, PAUSE, MEASURE = auto(), auto(), auto()
def __init__(self):
self._state = StopWatch.WAIT
@property
def state(self):
return self._state
def start_stop(self):
if self.state == StopWatch.WAIT:
self._state = StopWatch.MEASURE
# process that counts up the time...
elif self.state == StopWatch.MEASURE:
self._state = StopWatch.PAUSE
# do something
elif self.state == StopWatch.PAUSE:
self._state = StopWatch.MEASURE
# do something
else:
raise ValueError(self.__class__.__name__ + " has an unexpected state: {}".format(self.state))
def reset(self):
if self.state == StopWatch.PAUSE:
self._state = StopWatch.WAIT
# do something
前回は状態を列挙する Enum
を作成したが、今回はもう少し簡便な実装にしている。
なお、今はふるまいの状態遷移に興味があるので、時間をカウントする、時間を表示するといったストップウォッチとしての処理については割愛している。
今度はふるまいの違いによって状態を表すように実装する。さらに、状態遷移表self._TRANSIT
を利用する2 ことで、状態遷移全体の見通しをよくする。
from enum import auto
class StopWatch:
WAIT, PAUSE, MEASURE = auto(), auto(), auto()
def __init__(self):
self._TRANSIT = {StopWatch.WAIT: (self.start, lambda *args: None),
StopWatch.PAUSE: (self.start, self.reset_time),
StopWatch.MEASURE: (self.pause, lambda *args: None)}
self._TRANSIT_REVERSED = {v: k for k, v in self._TRANSIT.items()}
self.start_stop, self.reset = self._TRANSIT[StopWatch.WAIT]
@property
def state(self):
return self._TRANSIT_REVERSED[self.start_stop, self.reset]
def start(self):
self.start_stop, self.reset = self._TRANSIT[StopWatch.MEASURE]
def pause(self):
self.start_stop, self.reset = self._TRANSIT[StopWatch.PAUSE]
def reset_time(self):
self.start_stop, self.reset = self._TRANSIT[StopWatch.WAIT]
-
第4回 「状態遷移図」と「状態遷移表」で見えるもの:上流工程で効く,「テストの考え方」|gihyo.jp … 技術評論社 ↩
-
前回の投稿 (状態とふるまいを持つモデルを実装する)に対していただいたコメントのアイデアを拝借いたしました。 ↩