Skip to main content
[Edit removed during grace period]
Source Link
deleted 128 characters in body; edited tags
Source Link
AlexV
  • 7.4k
  • 2
  • 24
  • 47

I'm learning about the command design pattern and would like you to critique it for division of responsibility, especially with regards to how the robot "undoes" commands it previously executed and where those commands are stored (inside RobotControllerRobotController)

If I were to create another receiver (say, FlyingRobotFlyingRobot), is it customary to create another set of concrete Commandscommands for it? What would I have to change if I wanted receivers to share some commands but not others?)

L Maneuver:
Wallie: Moving [Forward, 5].
Wallie: Moving [Left, 4].
Wallie: Moving [Right, 4].
Wallie: Moving [Backwards, 5].
3-2-1 Stroll:
Wallie: Moving [Forward, 3].
Wallie: Moving [Forward, 2].
Wallie: Moving [Forward, 1].
Wallie: Moving [Backwards, 1].
Wallie: Moving [Backwards, 2].
Wallie: Moving [Backwards, 3].
Counterclockwise Circle Maneuver:
Wallie: Moving [Forward, 3].
Wallie: Moving [Left, 3].
Wallie: Moving [Backwards, 3].
Wallie: Moving [Right, 3].
Wallie: Moving [Left, 3].
Wallie: Moving [Forward, 3].
Wallie: Moving [Right, 3].
Wallie: Moving [Backwards, 3].
Eve's Turn:
Eve: Moving [Forward, 5].
Eve: Moving [Left, 4].
Eve: Moving [Forward, 3].
Eve: Moving [Left, 3].
Eve: Moving [Backwards, 3].
Eve: Moving [Right, 3].
Eve: Moving [Left, 3].
Eve: Moving [Forward, 3].
Eve: Moving [Right, 3].
Eve: Moving [Backwards, 3].
Eve: Moving [Right, 4].
Eve: Moving [Backwards, 5].
L Maneuver:
Wallie: Moving [Forward, 5].
Wallie: Moving [Left, 4].
Wallie: Moving [Right, 4].
Wallie: Moving [Backwards, 5].
3-2-1 Stroll:
Wallie: Moving [Forward, 3].
Wallie: Moving [Forward, 2].
Wallie: Moving [Forward, 1].
Wallie: Moving [Backwards, 1].
Wallie: Moving [Backwards, 2].
Wallie: Moving [Backwards, 3].
Counterclockwise Circle Maneuver:
Wallie: Moving [Forward, 3].
Wallie: Moving [Left, 3].
Wallie: Moving [Backwards, 3].
Wallie: Moving [Right, 3].
Wallie: Moving [Left, 3].
Wallie: Moving [Forward, 3].
Wallie: Moving [Right, 3].
Wallie: Moving [Backwards, 3].
Eve's Turn:
Eve: Moving [Forward, 5].
Eve: Moving [Left, 4].
Eve: Moving [Forward, 3].
Eve: Moving [Left, 3].
Eve: Moving [Backwards, 3].
Eve: Moving [Right, 3].
Eve: Moving [Left, 3].
Eve: Moving [Forward, 3].
Eve: Moving [Right, 3].
Eve: Moving [Backwards, 3].
Eve: Moving [Right, 4].
Eve: Moving [Backwards, 5].

I'm learning about the command design pattern and would like you to critique it for division of responsibility, especially with regards to how the robot "undoes" commands it previously executed and where those commands are stored (inside RobotController)

If I were to create another receiver (say, FlyingRobot, is it customary to create another set of concrete Commands for it? What would I have to change if I wanted receivers to share some commands but not others?)

L Maneuver:
Wallie: Moving [Forward, 5].
Wallie: Moving [Left, 4].
Wallie: Moving [Right, 4].
Wallie: Moving [Backwards, 5].
3-2-1 Stroll:
Wallie: Moving [Forward, 3].
Wallie: Moving [Forward, 2].
Wallie: Moving [Forward, 1].
Wallie: Moving [Backwards, 1].
Wallie: Moving [Backwards, 2].
Wallie: Moving [Backwards, 3].
Counterclockwise Circle Maneuver:
Wallie: Moving [Forward, 3].
Wallie: Moving [Left, 3].
Wallie: Moving [Backwards, 3].
Wallie: Moving [Right, 3].
Wallie: Moving [Left, 3].
Wallie: Moving [Forward, 3].
Wallie: Moving [Right, 3].
Wallie: Moving [Backwards, 3].
Eve's Turn:
Eve: Moving [Forward, 5].
Eve: Moving [Left, 4].
Eve: Moving [Forward, 3].
Eve: Moving [Left, 3].
Eve: Moving [Backwards, 3].
Eve: Moving [Right, 3].
Eve: Moving [Left, 3].
Eve: Moving [Forward, 3].
Eve: Moving [Right, 3].
Eve: Moving [Backwards, 3].
Eve: Moving [Right, 4].
Eve: Moving [Backwards, 5].

I'm learning about the command design pattern and would like you to critique it for division of responsibility, especially with regards to how the robot "undoes" commands it previously executed and where those commands are stored (inside RobotController)

If I were to create another receiver (say, FlyingRobot), is it customary to create another set of concrete commands for it? What would I have to change if I wanted receivers to share some commands but not others?

L Maneuver:
Wallie: Moving [Forward, 5].
Wallie: Moving [Left, 4].
Wallie: Moving [Right, 4].
Wallie: Moving [Backwards, 5].
3-2-1 Stroll:
Wallie: Moving [Forward, 3].
Wallie: Moving [Forward, 2].
Wallie: Moving [Forward, 1].
Wallie: Moving [Backwards, 1].
Wallie: Moving [Backwards, 2].
Wallie: Moving [Backwards, 3].
Counterclockwise Circle Maneuver:
Wallie: Moving [Forward, 3].
Wallie: Moving [Left, 3].
Wallie: Moving [Backwards, 3].
Wallie: Moving [Right, 3].
Wallie: Moving [Left, 3].
Wallie: Moving [Forward, 3].
Wallie: Moving [Right, 3].
Wallie: Moving [Backwards, 3].
Eve's Turn:
Eve: Moving [Forward, 5].
Eve: Moving [Left, 4].
Eve: Moving [Forward, 3].
Eve: Moving [Left, 3].
Eve: Moving [Backwards, 3].
Eve: Moving [Right, 3].
Eve: Moving [Left, 3].
Eve: Moving [Forward, 3].
Eve: Moving [Right, 3].
Eve: Moving [Backwards, 3].
Eve: Moving [Right, 4].
Eve: Moving [Backwards, 5].
Source Link

Command Design Pattern Implementation: Moving Robot with Undo-movement (Python)

I'm learning about the command design pattern and would like you to critique it for division of responsibility, especially with regards to how the robot "undoes" commands it previously executed and where those commands are stored (inside RobotController)

Everything seems to work correctly.

I do have a question:

If I were to create another receiver (say, FlyingRobot, is it customary to create another set of concrete Commands for it? What would I have to change if I wanted receivers to share some commands but not others?)

from collections import deque, namedtuple

# Invoker
class RobotController(object):
    inst_count = 0
    def __init__(self, name=None, commands=None):
        if not name:
            self.name = f"RobotController #{RobotController.inst_count}"
        else:
            self.name = name
        if not commands:
            self._commands = deque()
        else:
            self._commands = commands
        self.previously_executed_cmds = deque()
        RobotController.inst_count += 1

    def __str__(self):
        return self.name

    @property
    def commands(self):
        return self._commands

    @commands.setter
    def commands(self, other):
        for cmd in other:
            self._commands.append(cmd)

    # Batch-Execute
    def ExecuteCommand(self, batch_size=None):
        '''Execute commands from queue of commands, up to batch_size number of commands'''
        if not batch_size:
            batch_size = len(self.commands)

        execute_results = deque()
        while len(self.commands) >= 1 and batch_size > 0:
            cmd = self.commands.popleft() # Get the next command in the queue
            execute_results.append((cmd, cmd.Execute()))
            self.previously_executed_cmds.append(cmd)
            batch_size -= 1
        return execute_results

    def UndoCommands(self):
        '''Execute previously executed command but in reverse.'''
        while self.previously_executed_cmds:
            cmd = self.previously_executed_cmds.pop()
            cmd.Undo()

# Receiver
class Robot(object):
    inst_count = 0
    supportedMoveAction = ["Forward", "Backwards", "Left", "Right", "Stop", "No-Op"]
    def __init__(self, name=None):
        if not name:
            self.name = f"Robot #{Robot.inst_count}"
        else:
            self.name = name
        Robot.inst_count += 1

    def __str__(self):
        return self.name

    def Move(self, moveAction, distance):
        if moveAction in Robot.supportedMoveAction:
            actiontaken = f'{self}: Moving [{moveAction}, {distance}].'
        else:
            actiontaken = f'{self}: Move Failed: Unsupported move action {moveAction}.'
        print(actiontaken) # Perform the action
        return actiontaken

# Command (Base/Abstract)
class RobotCommand(object):
    def __init__(self):
        pass
    # API Common to all Command subclasses
    def Execute(self):
        pass # Performs a Robot Command
    def Undo(self):
        pass # Reverts Robot to the state it was in before .Execute() was called

# Command (Concrete): Aware of the Receiver object's API
class Move(RobotCommand):
    undo_movement = {'Forward':'Backwards', 'Backwards':'Forward', 'Left':'Right', 'Right':'Left', 'Stop':'Stop', 'No-Op':'No-Op'}

    def __init__(self, receiver, action="No-Op", distance=0):
        self.receiver = receiver
        self.action = action
        self.distance = distance

    def Execute(self):
        return self.receiver.Move(self.action, self.distance)

    def Undo(self):
        '''
        Notice that the Move Command stores state information about: 
            1. What Receiver object was called (the specific Robot instance)
            2. The details with which to pass to Robot.Move() (i.e., `action` and `distance`)
        This necessarily relinquishes the responsibility of the Receiver to implement
        "Undo" and store state information (i.e., what it was previously told to do)
        ''' 
        self.receiver.Move(Move.undo_movement[self.action], self.distance)

# To avoid duplicating code and as a way to assign maneuvers to arbitrary robot receivers
def get_maneuver(robot_receiver):
    L_maneuver = deque([
        Move(robot_receiver, 'Forward', 5),
        Move(robot_receiver, 'Left', 4)
    ])

    cww_maneuver = deque([
        Move(robot_receiver, 'Forward', 3), 
        Move(robot_receiver, 'Left', 3),
        Move(robot_receiver, 'Backwards', 3), 
        Move(robot_receiver, 'Right', 3)
    ])
    return L_maneuver, cww_maneuver

if __name__ == '__main__':
    maneuvers = namedtuple('Maneuvers', ['LManeuver', 'CcwCircle'])

    # Create our actors
    Wallie = Robot('Wallie')
    Eve = Robot('Eve')

    # Initiatize their maneuvers
    wallie_maneuvers = maneuvers(*get_maneuver(Wallie))
    eve_maneuvers = maneuvers(*get_maneuver(Eve))

    # Initialize a controller
    Nasah = RobotController(name='Nasah')

    # Should be harmless if no commands were previously provided
    Nasah.ExecuteCommand()
    Nasah.UndoCommands()

    print('L Maneuver:')
    Nasah.commands = wallie_maneuvers.LManeuver
    Nasah.ExecuteCommand()
    Nasah.UndoCommands()

    print('3-2-1 Stroll:')
    Nasah.commands.append(Move(Wallie, 'Forward', 3))
    Nasah.commands.append(Move(Wallie, 'Forward', 2))
    Nasah.commands.append(Move(Wallie, 'Forward', 1))
    Nasah.ExecuteCommand(batch_size=1)
    Nasah.ExecuteCommand(batch_size=2)
    Nasah.UndoCommands() # Backwards 1, Backwards 2, Backwards 3

    print('Counterclockwise Circle Maneuver:')
    Nasah.commands = wallie_maneuvers.CcwCircle
    Nasah.ExecuteCommand()
    Nasah.UndoCommands()

    print('Eve\'s Turn:')
    Nasah.commands = eve_maneuvers.LManeuver
    Nasah.commands = eve_maneuvers.CcwCircle
    Nasah.UndoCommands() # Should do nothing
    Nasah.ExecuteCommand() # Eve to perform LManeuver and CcwCircle
    Nasah.UndoCommands()

Output

L Maneuver:
Wallie: Moving [Forward, 5].
Wallie: Moving [Left, 4].
Wallie: Moving [Right, 4].
Wallie: Moving [Backwards, 5].
3-2-1 Stroll:
Wallie: Moving [Forward, 3].
Wallie: Moving [Forward, 2].
Wallie: Moving [Forward, 1].
Wallie: Moving [Backwards, 1].
Wallie: Moving [Backwards, 2].
Wallie: Moving [Backwards, 3].
Counterclockwise Circle Maneuver:
Wallie: Moving [Forward, 3].
Wallie: Moving [Left, 3].
Wallie: Moving [Backwards, 3].
Wallie: Moving [Right, 3].
Wallie: Moving [Left, 3].
Wallie: Moving [Forward, 3].
Wallie: Moving [Right, 3].
Wallie: Moving [Backwards, 3].
Eve's Turn:
Eve: Moving [Forward, 5].
Eve: Moving [Left, 4].
Eve: Moving [Forward, 3].
Eve: Moving [Left, 3].
Eve: Moving [Backwards, 3].
Eve: Moving [Right, 3].
Eve: Moving [Left, 3].
Eve: Moving [Forward, 3].
Eve: Moving [Right, 3].
Eve: Moving [Backwards, 3].
Eve: Moving [Right, 4].
Eve: Moving [Backwards, 5].