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

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

```python: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` の実装をみる必要がある。たとえば、状態の表現に列挙値を使用することで、見通しがよくなることがある。

```python: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.state` に `on` などの想定外の値は代入され得る。

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

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

```python: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` に値をセットできないようにする。

```python
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()` を用意したほうがよい場合もある。

```python
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` に機能追加することを踏まえて実装を変えておく。
これまでは、ふるまいが状態に応じて変わる（メソッドの処理が状態によって異なる）ように実装していたが、ふるまいの違いによって状態を表す（処理の違いによって状態が異なる）ように実装することもできる。

```python
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` の機能に違いはない。