状態とふるまいを持つモデルを実装する (2)

前回 （[状態とふるまいを持つモデルを実装する](https://qiita.com/torajiro1220/items/e037ec124b6131defb89)）では、状態とふるまいを持つプリミティブなモデル `Switch` を実装した。今回は `Switch` に状態を追加して拡張を試みる。
ストップウォッチのような、三つの状態、待機状態`WAIT`, 計測状態`MEASURE`, 停止状態`PAUSE` があるモデルを考える。ストップウォッチには、二つのボタン `start_stop()`, `reset()` があり、以下のような状態とふるまいを想定する[^1]。

- 待機状態`WAIT` でボタン`start_stop()` が押下されると、計測状態`MEASURE`になる。
- 計測状態`MEASURE` でボタン`start_stop()` が押下されると、停止状態`PAUSE`になる。
- 停止状態`PAUSE` でボタン`start_stop()` が押下されると再び、計測状態`MEASURE`になる。
- また、停止状態`PAUSE`でボタン`reset()` が押下されると、待機状態`WAIT`になる。

このモデルは以下のように状態遷移する。

```python
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
```

まずは、ふるまいが状態に応じて変わるように実装する。

```python
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] ことで、状態遷移全体の見通しをよくする。

```python
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]
```

[^1]: [第4回　「状態遷移図」と「状態遷移表」で見えるもの：上流工程で効く，「テストの考え方」｜gihyo.jp … 技術評論社](https://gihyo.jp/dev/serial/01/test_up/0004)
[^2]: 前回の投稿 （[状態とふるまいを持つモデルを実装する](https://qiita.com/torajiro1220/items/e037ec124b6131defb89)）に対していただいたコメントのアイデアを拝借いたしました。