implemented command pattern.
This commit is contained in:
94
pypatterns/behavioral/command.py
Normal file
94
pypatterns/behavioral/command.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
from abc import ABCMeta, abstractmethod
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidActionException(Exception):
|
||||||
|
"""
|
||||||
|
Exception for when an invalid action is called on a Receiver.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidInvokerCommandException(Exception):
|
||||||
|
"""
|
||||||
|
Exception for when an invalid command is given to an Invoker to execute.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Receiver(object, metaclass=ABCMeta):
|
||||||
|
"""
|
||||||
|
Abstract receiver class as part of the Command pattern.
|
||||||
|
"""
|
||||||
|
def action(self, name, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Delegates which method to be called for a desired action.
|
||||||
|
|
||||||
|
@param name: The name of the action to execute.
|
||||||
|
@type name: str
|
||||||
|
@param args: Any arguments for the action.
|
||||||
|
@param kwargs: Any keyword arguments for the action.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return getattr(self, name)(*args, **kwargs)
|
||||||
|
except AttributeError:
|
||||||
|
raise InvalidActionException
|
||||||
|
|
||||||
|
|
||||||
|
class Command(object, metaclass=ABCMeta):
|
||||||
|
"""
|
||||||
|
Abstract Command class as part of the Command pattern.
|
||||||
|
"""
|
||||||
|
def __init__(self, receiver):
|
||||||
|
"""
|
||||||
|
Initialize a new command instance.
|
||||||
|
|
||||||
|
@param receiver: The receiver for this command to use.
|
||||||
|
@type receiver: Receiver
|
||||||
|
"""
|
||||||
|
self._receiver = receiver
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def execute(self):
|
||||||
|
"""
|
||||||
|
Abstract method for executing an action.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def unexecute(self):
|
||||||
|
"""
|
||||||
|
Abstract method for unexecuting an action.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Invoker(object, metaclass=ABCMeta):
|
||||||
|
"""
|
||||||
|
Abstract Invoker class as part of the Command pattern.
|
||||||
|
"""
|
||||||
|
def __init__(self, valid_commands):
|
||||||
|
"""
|
||||||
|
Initialize a new Invoker instance.
|
||||||
|
|
||||||
|
@param valid_commands: A list of command classes this invoker can handle.
|
||||||
|
"""
|
||||||
|
self._history = []
|
||||||
|
self._valid_commands = valid_commands
|
||||||
|
|
||||||
|
def execute(self, command):
|
||||||
|
"""
|
||||||
|
Execute a command.
|
||||||
|
|
||||||
|
@param command: A command for the invoker to execute.
|
||||||
|
@type command: Command
|
||||||
|
"""
|
||||||
|
if command.__class__ not in self._valid_commands:
|
||||||
|
raise InvalidInvokerCommandException
|
||||||
|
else:
|
||||||
|
self._history.append(command)
|
||||||
|
return command.execute()
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
"""
|
||||||
|
Undo the last command.
|
||||||
|
"""
|
||||||
|
return self._history.pop().unexecute()
|
||||||
190
tests/behavioral_tests/test_command.py
Normal file
190
tests/behavioral_tests/test_command.py
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
from pypatterns.behavioral.command import InvalidActionException, InvalidInvokerCommandException, \
|
||||||
|
Receiver, Command, Invoker
|
||||||
|
|
||||||
|
|
||||||
|
class ReceiverTestCase(TestCase):
|
||||||
|
"""
|
||||||
|
Unit testing class for the Receiver class.
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
Initialize testing data.
|
||||||
|
"""
|
||||||
|
class Thermostat(Receiver):
|
||||||
|
|
||||||
|
def raise_temp(self, amount):
|
||||||
|
return "Temperature raised by {0} degrees".format(amount)
|
||||||
|
|
||||||
|
def lower_temp(self, amount):
|
||||||
|
return "Temperature lowered by {0} degrees".format(amount)
|
||||||
|
|
||||||
|
self.thermostat = Thermostat()
|
||||||
|
|
||||||
|
def test_valid_action(self):
|
||||||
|
"""
|
||||||
|
Test the action method with a valid action.
|
||||||
|
|
||||||
|
@raise AssertionError: If the test fails.
|
||||||
|
"""
|
||||||
|
self.assertEquals("Temperature raised by 5 degrees", self.thermostat.action('raise_temp', 5))
|
||||||
|
self.assertEquals("Temperature lowered by 5 degrees", self.thermostat.action('lower_temp', 5))
|
||||||
|
|
||||||
|
def test_invalid_action(self):
|
||||||
|
"""
|
||||||
|
Test the action method with an invalid action.
|
||||||
|
|
||||||
|
@raise AssertionError: If the test fails.
|
||||||
|
"""
|
||||||
|
with self.assertRaises(InvalidActionException):
|
||||||
|
self.thermostat.action('foo')
|
||||||
|
|
||||||
|
|
||||||
|
class CommandTestCase(TestCase):
|
||||||
|
"""
|
||||||
|
Unit testing class for the Command class.
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
Initialize testing data.
|
||||||
|
"""
|
||||||
|
class Thermostat(Receiver):
|
||||||
|
def raise_temp(self, amount):
|
||||||
|
return "Temperature raised by {0} degrees".format(amount)
|
||||||
|
|
||||||
|
def lower_temp(self, amount):
|
||||||
|
return "Temperature lowered by {0} degrees".format(amount)
|
||||||
|
|
||||||
|
class RaiseTempCommand(Command):
|
||||||
|
|
||||||
|
def __init__(self, receiver, amount=5):
|
||||||
|
super().__init__(receiver)
|
||||||
|
self.amount = amount
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
return self._receiver.action('raise_temp', self.amount)
|
||||||
|
|
||||||
|
def unexecute(self):
|
||||||
|
return self._receiver.action('lower_temp', self.amount)
|
||||||
|
|
||||||
|
class LowerTempCommand(Command):
|
||||||
|
|
||||||
|
def __init__(self, receiver, amount=5):
|
||||||
|
super().__init__(receiver)
|
||||||
|
self.amount = amount
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
return self._receiver.action('lower_temp', self.amount)
|
||||||
|
|
||||||
|
def unexecute(self):
|
||||||
|
return self._receiver.action('raise_temp', self.amount)
|
||||||
|
|
||||||
|
self.thermostat = Thermostat()
|
||||||
|
self.raise_temp_command_class = RaiseTempCommand
|
||||||
|
self.lower_temp_command_class = LowerTempCommand
|
||||||
|
|
||||||
|
def test_execute(self):
|
||||||
|
"""
|
||||||
|
Test the execute method.
|
||||||
|
|
||||||
|
@raise AssertionError: If the test fails.
|
||||||
|
"""
|
||||||
|
raise_temp_command = self.raise_temp_command_class(self.thermostat, 10)
|
||||||
|
lower_temp_command = self.lower_temp_command_class(self.thermostat, 5)
|
||||||
|
|
||||||
|
self.assertEquals("Temperature raised by 10 degrees", raise_temp_command.execute())
|
||||||
|
self.assertEquals("Temperature lowered by 5 degrees", lower_temp_command.execute())
|
||||||
|
|
||||||
|
|
||||||
|
class InvokerTestCase(TestCase):
|
||||||
|
"""
|
||||||
|
Unit testing class for the Invoker class.
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
Initialize testing data.
|
||||||
|
"""
|
||||||
|
class Thermostat(Receiver):
|
||||||
|
def raise_temp(self, amount):
|
||||||
|
return "Temperature raised by {0} degrees".format(amount)
|
||||||
|
|
||||||
|
def lower_temp(self, amount):
|
||||||
|
return "Temperature lowered by {0} degrees".format(amount)
|
||||||
|
|
||||||
|
class RaiseTempCommand(Command):
|
||||||
|
def __init__(self, receiver, amount=5):
|
||||||
|
super().__init__(receiver)
|
||||||
|
self.amount = amount
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
return self._receiver.action('raise_temp', self.amount)
|
||||||
|
|
||||||
|
def unexecute(self):
|
||||||
|
return self._receiver.action('lower_temp', self.amount)
|
||||||
|
|
||||||
|
class LowerTempCommand(Command):
|
||||||
|
def __init__(self, receiver, amount=5):
|
||||||
|
super().__init__(receiver)
|
||||||
|
self.amount = amount
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
return self._receiver.action('lower_temp', self.amount)
|
||||||
|
|
||||||
|
def unexecute(self):
|
||||||
|
return self._receiver.action('raise_temp', self.amount)
|
||||||
|
|
||||||
|
class Worker(Invoker):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__([LowerTempCommand, RaiseTempCommand])
|
||||||
|
|
||||||
|
self.worker = Worker()
|
||||||
|
self.receiver = Thermostat()
|
||||||
|
self.lower_temp_command = LowerTempCommand(self.receiver)
|
||||||
|
self.raise_temp_command = RaiseTempCommand(self.receiver)
|
||||||
|
|
||||||
|
def test_valid_execute(self):
|
||||||
|
"""
|
||||||
|
Test the execute method with a valid command for the Worker Invoker.
|
||||||
|
|
||||||
|
@raise AssertionError: If the test fails.
|
||||||
|
"""
|
||||||
|
self.assertEquals("Temperature lowered by 5 degrees", self.worker.execute(self.lower_temp_command))
|
||||||
|
self.assertEquals("Temperature raised by 5 degrees", self.worker.execute(self.raise_temp_command))
|
||||||
|
|
||||||
|
def test_invalid_execute(self):
|
||||||
|
"""
|
||||||
|
Test the execute method with an invalid command for the Worker Invoker.
|
||||||
|
|
||||||
|
@raise AssertionError: If the test fails.
|
||||||
|
"""
|
||||||
|
class Light(Receiver):
|
||||||
|
|
||||||
|
def turn_on(self):
|
||||||
|
return "Light turned on"
|
||||||
|
|
||||||
|
def turn_off(self):
|
||||||
|
return "Light turned off"
|
||||||
|
|
||||||
|
class TurnOnLightCommand(Command):
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
return self._receiver.action('turn_on')
|
||||||
|
|
||||||
|
def unexecute(self):
|
||||||
|
return self._receiver.action('turn_off')
|
||||||
|
|
||||||
|
with self.assertRaises(InvalidInvokerCommandException):
|
||||||
|
self.worker.execute(TurnOnLightCommand(Light))
|
||||||
|
|
||||||
|
def test_undo(self):
|
||||||
|
"""
|
||||||
|
Test the undo method.
|
||||||
|
|
||||||
|
@raise AssertionError: If the test fails.
|
||||||
|
"""
|
||||||
|
self.worker.execute(self.raise_temp_command)
|
||||||
|
|
||||||
|
self.assertIn(self.raise_temp_command, self.worker._history)
|
||||||
|
self.assertEquals("Temperature lowered by 5 degrees", self.worker.undo())
|
||||||
|
self.assertNotIn(self.raise_temp_command, self.worker._history)
|
||||||
Reference in New Issue
Block a user