LoginSignup
0
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-07-25

前回 (状態とふるまいを持つモデルを実装する)では、状態とふるまいを持つプリミティブなモデル 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]
0
1
4

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
0
1