Consider switches (lighting power buttons, elevator floor destination buttons, etc.) as primitive models with state and behavior.
The switch has two states, a pressed state ʻon and a non-pressed state ʻoff
. Also, the behavior of the switch switch ()
changes depending on the state. If it is in the pressed state ʻon, it will be in the non-pressed state ʻoff
, and if it is not pressed, it will be in the pressed state ʻ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
For the possible values (states) of Switch.state
, you need to look at the implementation of Switch
. For example, using enumerated values to represent states can improve visibility.
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
Also, if the default value of the constructor argument state
is set to SwitchState
, a warning will be issued for values other than SwitchState
. However, unexpected values such as ʻon can be assigned to
Switch.state` as follows:
switch.state = True
switch.switch() # RuntimeError: Switch has an unexpected state: True
You don't notice that an unexpected value was assigned until switch ()
was called. Therefore, it is easier to send a TypeError when an unexpected value is assigned to Switch.state
as shown below.
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'>
If you don't want the state of Switch
to change from outside the class, make it impossible to set a value in 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
If you want to change the state of Switch
from outside the class, it is better to prepare methodsturn_on ()
,turn_off ()
for state transition rather than surrendering Switch.state
to outside the class. In some cases.
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
Finally, we will change the implementation based on the addition of functions to Switch
later.
Until now, it was implemented so that the behavior changes according to the state (method processing differs depending on the state), but it can also be implemented so that the state is represented by the difference in behavior (the state differs depending on the processing difference). it can.
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
There is no difference in the functionality of Switch
, only the implementation is different.