Python Command Design Pattern

1. Definition

The Command Design Pattern encapsulates a request as an object, thereby allowing users to parameterize other objects with different requests, queue requests, and support undoable operations. It decouples the object that invokes the operation from the one that knows how to execute it.

2. Problem Statement

Consider a scenario where you are building a remote control for electronic devices. The remote needs a way to dynamically assign buttons to control various devices and actions, such as turning a TV on/off or adjusting the volume of a speaker. Hardcoding each button's functionality makes the system inflexible and hard to extend.

3. Solution

Represent each operation as an object. This allows you to decouple the sender (the remote control) from the receiver (the device being controlled). The sender only knows about the command interface, not the concrete command or receiver.

4. Real-World Use Cases

1. Macro recording in software applications.

2. Undo and redo functionality in software editors.

3. Task scheduling and management systems.

4. Remote controls for electronic devices.

5. Implementation Steps

1. Define a command interface with an execute method.

2. Create one or more concrete classes that implement this command interface, encapsulating a receiver and an action.

3. Define classes that act as senders. These will use command objects to carry out an action.

6. Implementation in Python

# Step 1: Command interface
class Command:
    def execute(self):
        pass
# Step 2: Concrete Command classes
class TurnOnTVCommand(Command):
    def __init__(self, tv):
        self._tv = tv
    def execute(self):
        self._tv.turn_on()
class TurnOffTVCommand(Command):
    def __init__(self, tv):
        self._tv = tv
    def execute(self):
        self._tv.turn_off()
# Receiver class
class TV:
    def turn_on(self):
        print("TV is turned on!")
    def turn_off(self):
        print("TV is turned off!")
# Invoker class
class RemoteControl:
    def set_command(self, command):
        self._command = command
    def press_button(self):
        self._command.execute()
# Client code
tv = TV()
turn_on = TurnOnTVCommand(tv)
turn_off = TurnOffTVCommand(tv)
remote = RemoteControl()
remote.set_command(turn_on)
remote.press_button()
remote.set_command(turn_off)
remote.press_button()

Output:

"TV is turned on!"
"TV is turned off!"

Explanation:

1. The Command interface has an execute method which concrete command classes like TurnOnTVCommand and TurnOffTVCommand implement.

2. These command classes encapsulate a receiver (TV in this case) and call its methods.

3. The RemoteControl acts as an invoker. It's given a command object, which it calls when its button is pressed.

4. In the client code, we create a TV (receiver), assign commands to the remote (invoker), and simulate button presses.

7. When to use?

The Command Pattern is useful when:

1. Decoupling is needed between objects that issue commands and those that perform the action.

2. Commands need to be queued, undone, or logged.

3. The invoker should be decoupled from the system's operations.


Comments