状態とふるまいを持つプリミティブなモデルとして、スイッチ(照明の電源ボタン、エレベーターの各階行先ボタンなど)を考える。
スイッチには二つの状態、押されている状態on
と押されていない状態off
がある。また、スイッチのふるまいswitch()
は、状態によってかわる。押されている状態on
であれば、押されていない状態off
になり、押されていない状態off
であれば押されている状態on
になる。
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
の実装をみる必要がある。たとえば、状態の表現に列挙値を使用することで、見通しがよくなることがある。
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
などの想定外の値は代入され得る。
switch.state = True
switch.switch() # RuntimeError: Switch has an unexpected state: True
switch()
が呼ばれるまで想定外の値が代入されたことに気づかない。したがって、以下のように Switch.state
に想定外の値が代入されたタイミングで TypeError を送出するほうがやさしい。
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
の機能に違いはない。