Practical Design Patterns Part 1: The Command Pattern

3 min read Original article ↗
class Home(object): def __init__(self): self._fan_state = "OFF" self._fan_speed = 0 def turn_fan_on(self): self._fan_state = "ON" def turn_fan_off(self): self._fan_state = "OFF" def increase_fan_speed(self, offset=1): self._fan_speed = self._fan_speed + offset def decrease_fan_speed(self, offset=1): self._fan_speed = self._fan_speed – offset def get_fan_state(self): return self._fan_state def get_fan_speed(self): return self._fan_speed class SmartRemote(object): def __init__(self, home_instance): # Our smart home self.home = home_instance # stores a log of all the actions that we have performed self.command_stack = [] # Decoupled commands # Notice how the remote is not aware of how the commands are actually implemented. # It is just responsible for invoking the right command. self.fan_on_command_class = FanOnCommand self.fan_off_command_class = FanOffCommand self.fan_slow_command_class = FanSlowCommand self.fan_fast_command_class = None """ Since commands are completely decoupled from the invoker, we can easily update the commands without changing the invoker. """ def set_fan_fast_command(self, fan_fast_command): self.fan_fast_command_class = fan_fast_command """ Invokes the fan on command and adds the command to the stack """ def fan_on(self): if not self.fan_on_command_class: raise NotImplementedError("This operation is not supported") fan_on_command = self.fan_on_command_class() fan_on_command.execute(self.home) self.command_stack.append(fan_on_command) """ Invokes the fan off command and adds the command to the stack """ def fan_off(self): if not self.fan_off_command_class: raise NotImplementedError("This operation is not supported") fan_off_command = self.fan_off_command_class() fan_off_command.execute(self.home) self.command_stack.append(fan_off_command) """ Invokes the fan slow command and adds the command to the stack """ def fan_slow(self): if not self.fan_slow_command_class: raise NotImplementedError("This operation is not supported") fan_slow_command = self.fan_slow_command_class() fan_slow_command.execute(self.home) self.command_stack.append(fan_slow_command) """ Invokes the fan fast command and adds the command to the stack """ def fan_fast(self): if not self.fan_fast_command_class: raise NotImplementedError("This operation is not supported") fan_fast_command = self.fan_fast_command_class() fan_fast_command.execute(self.home) self.command_stack.append(fan_fast_command) """ Undo the last action that was performed. A major benefit of command pattern is that logging actions/commands becomes easy. This can be easily extended to create something like a undo/redo feature. """ def undo_last_action(self): last_command = self.command_stack.pop() last_command.undo(home) class BaseCommand(object): def execute(self, home_instance): raise NotImplementedError def undo(self, home_instance): raise NotImplementedError class FanOnCommand(BaseCommand): def __init__(self): self._prev_fan_state = None def execute(self, home_instance): home_instance.turn_fan_on() print ("Fan state is now {0}".format(home_instance.get_fan_state())) def undo(self, home_instance): home_instance.turn_fan_off() print("UNDO: Fan state is now {0}".format(home_instance.get_fan_state())) class FanOffCommand(BaseCommand): def __init__(self): self._prev_fan_state = None def execute(self, home_instance): home_instance.turn_fan_off() print("Fan state is now {0}".format(home.get_fan_state())) def undo(self, home_instance): home_instance.turn_fan_on() print("UNDO: Fan state is now {0}".format(home_instance.get_fan_state())) class FanFastCommand(BaseCommand): def __init__(self): self._prev_fan_speed = None def execute(self, home_instance): home_instance.increase_fan_speed(offset=1) print("Fan speed is now {0}".format(home_instance.get_fan_speed())) def undo(self, home_instance): home_instance.decrease_fan_speed(offset=1) print("UNDO: Fan speed is now {0}".format(home_instance.get_fan_speed())) class FanSlowCommand(BaseCommand): def execute(self, home_instance): home_instance.decrease_fan_speed(offset=1) print("Fan speed is now {0}".format(home_instance.get_fan_speed())) def undo(self, home_instance): home_instance.increase_fan_speed(offset=1) print("UNDO: Fan speed is now {0}".format(home_instance.get_fan_speed())) if __name__ == '__main__': home = Home() smart_remote = SmartRemote(home) # Since the commands and the invoker are decoupled, we can easily # change the commands. smart_remote.fan_fast_command_class = FanFastCommand smart_remote.fan_on() smart_remote.fan_fast() smart_remote.fan_fast() smart_remote.fan_slow() # Notice how easy it is to undo actions. # This is also useful in keeping a log of actions that have taken place. smart_remote.undo_last_action() smart_remote.undo_last_action() # Like a responsible citizen always turn the fan off when leaving the room. smart_remote.fan_off()