LoginSignup
0
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-07-18

状態とふるまいを持つプリミティブなモデルとして、スイッチ(照明の電源ボタン、エレベーターの各階行先ボタンなど)を考える。
スイッチには二つの状態、押されている状態on と押されていない状態off がある。また、スイッチのふるまいswitch() は、状態によってかわる。押されている状態on であれば、押されていない状態off になり、押されていない状態off であれば押されている状態on になる。

example1
class Switch:
    def __init__(self, state="off"):
        self.state = state

    def switch(self):
        if self.state == "on":
            self.state = "off"
        elif self.state == "off":
            self.state = "on"
        else:
            # I wonder whether it should be ValueError().
            raise RuntimeError(self.__class__.__name__ + " has an unexpected state: {}".format(self.state))


switch = Switch()
assert switch.state == "off"
switch.switch()
assert switch.state == "on"
switch.switch()
assert switch.state == "off"
switch.state = True
switch.switch()
-> RuntimeError: Switch has an unexpected state: True

Switch.state がとり得る値(状態)は Switch の実装をみる必要がある。たとえば、状態の表現に列挙値を使用することで、見通しがよくなることがある。

example2
from enum import Enum, auto


class SwitchState(Enum):
    ON = auto()
    OFF = auto()


class Switch:
    def __init__(self, state=SwitchState.OFF):
        self.state = state

    def switch(self):
        if self.state == SwitchState.ON:
            self.state = SwitchState.OFF
        elif self.state == SwitchState.OFF:
            self.state = SwitchState.ON
        else:
            raise RuntimeError(self.__class__.__name__ + " has an unexpected state: {}".format(self.state))


switch = Switch(SwitchState.ON)
switch.switch()
switch = Switch("on")  # warning: expected SwitchState type

また、コンストラクタの引数state のデフォルト値を SwitchState にすると、SwitchState 以外の値に対して warning になる。とはいえ、以下のように、Switch.stateon などの想定外の値は代入され得る。

switch.state = True
switch.switch()  # RuntimeError: Switch has an unexpected state: True

switch() が呼ばれるまで想定外の値が代入されたことに気づかない。したがって、以下のように Switch.state に想定外の値が代入されたタイミングで TypeError を送出するほうがやさしい。

example2
class Switch:
    def __init__(self, state=SwitchState.OFF):
        self._state = state

    def switch(self):
        if self._state == SwitchState.ON:
            self._state = SwitchState.OFF
        elif self._state == SwitchState.OFF:
            self._state = SwitchState.ON
        else:
            raise RuntimeError(self.__class__.__name__ + " has an unexpected state: {}".format(self._state))

    @property
    def state(self) -> SwitchState:
        return self._state

    @state.setter
    def state(self, value: SwitchState):
        if type(value) is not SwitchState:
            raise TypeError(self.__class__.__name__ + ".state must be SwitchState, but get: {}".format(type(value)))
        self._state = value


switch = Switch(SwitchState.ON)
switch.switch()
switch.state = True  # TypeError: Switch.state must be SwitchState, but get: <class 'bool'>

もし、Switch の状態をクラスの外部から変更されることを望まないなら、Switch.state に値をセットできないようにする。

class Switch:
    def __init__(self, state=SwitchState.OFF):
        self._state = state

    def switch(self):
        if self._state == SwitchState.ON:
            self._state = SwitchState.OFF
        elif self._state == SwitchState.OFF:
            self._state = SwitchState.ON
        else:
            raise RuntimeError(self.__class__.__name__ + " has an unexpected state: {}".format(self._state))

    @property
    def state(self) -> SwitchState:
        return self._state


switch = Switch(SwitchState.ON)
switch.switch()
switch.state = SwitchState.OFF  # Property cannot be set

Switch の状態をクラスの外部から変更したい場合は、Switch.state をクラスの外部に明け渡すより、状態遷移のためのメソッドturn_on(), turn_off() を用意したほうがよい場合もある。

class Switch:
    def __init__(self, state=SwitchState.OFF):
        self._state = state

    def switch(self):
        if self._state == SwitchState.ON:
            self.turn_off()
        elif self._state == SwitchState.OFF:
            self.turn_on()
        else:
            raise RuntimeError(self.__class__.__name__ + " has an unexpected state: {}".format(self._state))

    @property
    def state(self) -> SwitchState:
        return self._state

    def turn_on(self):
        self._state = SwitchState.ON

    def turn_off(self):
        self._state = SwitchState.OFF


switch = Switch()
switch.turn_off()
assert switch.state == SwitchState.OFF
switch.switch()
assert switch.state == SwitchState.ON

さいごに、以降で Switch に機能追加することを踏まえて実装を変えておく。
これまでは、ふるまいが状態に応じて変わる(メソッドの処理が状態によって異なる)ように実装していたが、ふるまいの違いによって状態を表す(処理の違いによって状態が異なる)ように実装することもできる。

from enum import Enum, auto


class SwitchState(Enum):
    ON = auto()
    OFF = auto()


class Switch:
    def __init__(self, state=SwitchState.OFF):
        self.switch = self.turn_on if state == SwitchState.OFF else self.turn_off

    @property
    def state(self) -> SwitchState:
        return SwitchState.OFF if self.switch == self.turn_on else SwitchState.ON

    def turn_on(self):
        self.switch = self.turn_off

    def turn_off(self):
        self.switch = self.turn_on


switch = Switch(SwitchState.ON)
switch.switch()
assert switch.state == SwitchState.OFF
switch.switch()
assert switch.state == SwitchState.ON

あくまで実装が異なるだけで、Switch の機能に違いはない。

0
3
2

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
3