TUTORIAL SERIES

Design Patterns in Python: Command

Command and Conquer

Amir Lavasani
6 min readNov 8, 2023

Have you encountered recurring coding challenges? Imagine having a toolbox of tried-and-true solutions readily available. That’s precisely what design patterns provide. In this series, we’ll explore what these patterns are and how they can elevate your coding skills.

Understanding the Command Pattern

Imagine you have a remote control that can operate various devices like a TV, stereo, or lights. The Command pattern lets you press a button on the remote, and the corresponding action is executed.

This separation of command and execution is the essence of the Command pattern.

What is the Command Design Pattern?

The Command design pattern is a behavioral pattern that encapsulates a request as an object, thereby allowing for parameterization of clients with different requests, queuing of requests, and logging of requests.

It separates the sender of a request from the receiver, providing a means for decoupling invokers and receivers.

When to use Command Pattern?

  1. Complex Interfaces: Ideal for applications with intricate user interfaces, simplifying action management, including undo/redo, toolbar customization, and shortcuts.
  2. Queueing Tasks: Useful for queuing and scheduling tasks or commands, facilitating orderly execution, vital for automation and task management.
  3. Undo/Redo Needs: Valuable in applications requiring strong undo and redo capabilities, like design software or text editors, improving user productivity.
Dall-E generated image with the following concept: A puppet with strings representing commands.

The Anatomy of a Command

In the Command Pattern, there are four key components:

  1. Command: This is an interface or an abstract class that declares an execute method. Concrete command classes implement this method, encapsulating the action to be performed.
  2. Concrete Command: These are the concrete implementations of the Command interface. They hold a reference to a receiver object and invoke specific actions on it.
  3. Receiver: The Receiver is the object that actually performs the action associated with the command.
  4. Invoker: The Invoker is responsible for storing and executing commands. It doesn’t need to know the specifics of how the command is executed.

By structuring your code this way, you achieve a high degree of decoupling. The sender (Invoker) doesn’t need to know anything about the receiver or how the command is handled.

Image from refactoring.guru

Implementing the Command Pattern in Python

Step 1: Command Interface

First, define the Command Interface using Python’s built-in abstract base class (ABC). This interface sets the contract for concrete command classes, including an abstract process method for specific actions.

from abc import ABC, abstractmethod

# Step 1: Define the Command Interface

class Command(ABC):
"""
Abstract base class for Command objects.
Concrete commands must implement the 'process' method.
"""

def __init__(self, receiver):
"""
Initialize a command with a receiver.

Args:
receiver: The object that will perform the action when the command is executed.
"""
self.receiver = receiver

@abstractmethod
def process(self):
"""
Execute the command's action.
"""
pass

Step 2: Concrete Command Implementation

Next, we create a concrete command class that inherits from the Command interface and implements the process method for the specific action.

# Step 2: Implement a Concrete Command

class ConcreteCommand(Command):
"""
Concrete command that performs an action through the receiver.
"""

def __init__(self, receiver):
"""
Initialize the concrete command.

Args:
receiver: The object that will perform the action when the command is executed.
"""
self.receiver = receiver

def process(self):
"""
Execute the command by delegating the action to the receiver.
"""
self.receiver.perform_action()

Step 3: Receiver

Now, implement the Receiver class to handle the actual action. The Receiver class includes the perform_action method executed by the concrete command.

# Step 3: Create a Receiver

class Receiver:
"""
Receiver class that performs an action.
"""

def perform_action(self):
"""
Perform the action.
"""
print('Action performed in receiver.')

Step 4: Invoker and Client

Finally, we create a Client and an Invoker to orchestrate the command execution. The Client instantiates the Receiver, Concrete Command, and Invoker. It sets the command for the Invoker and triggers the execution.

# Step 4: Create a Client and an Invoker

class Invoker:
"""
Invoker class that triggers the execution of a command.
"""

def __init__(self):
self.cmd = None

def command(self, cmd):
"""
Set the command to be executed.

Args:
cmd: The command to be executed.
"""
self.cmd = cmd

def execute(self):
"""
Execute the command by invoking its 'process' method.
"""
self.cmd.process()

if __name__ == "__main__":
# Create a Receiver object
receiver = Receiver()

# Create a concrete command and set its receiver
cmd = ConcreteCommand(receiver)

# Create an Invoker
invoker = Invoker()

# Set the command for the Invoker
invoker.command(cmd)

# Execute the command
invoker.execute()

GitHub Repo 🎉

Explore all code examples and design pattern implementations on GitHub!

Advantages

Implementing the Command Pattern offers several advantages, including:

  1. Decoupling: The pattern decouples the sender and receiver of a request, providing flexibility in managing commands and actions.
  2. Multi-Level Undo/Redo: The Command Pattern is ideal for implementing multi-level undo/redo functionality, as each command can be stored and reversed.
  3. Extensibility: New commands can be added without altering existing code, making the system more extensible.

Potential Drawbacks

In using the Command pattern, consider the following:

  1. Complexity: Implementing the pattern can increase code complexity.
  2. Overhead: It may introduce a minor memory and performance overhead.
  3. Applicability: Assess if the pattern suits your specific needs, as its benefits are most pronounced in scenarios requiring command decoupling and undo/redo functionality.
Dall-E generated image with the following concept: A simple line with blocks that represent commands queued for execution

Relations with Other Patterns — TL;DR;

Command vs. Chain of Responsibility

Command and Chain of Responsibility patterns are both about managing requests and actions, but they do so differently.

  • Command Pattern: Encapsulates a request as an object, decoupling the sender and receiver. It’s ideal for implementing multi-level undo/redo functionality and handling actions.
  • Chain of Responsibility Pattern: Passes a request through a chain of handlers. It’s used when you want to give more than one object a chance to handle a request without specifying the receiver explicitly. Unlike the Command Pattern, it doesn’t encapsulate a command but focuses on finding the right handler.

Command vs. Mediator

Command and Mediator patterns deal with connections between senders and receivers, yet they have distinct communication approaches.

  • Command Pattern: Focuses on encapsulating and decoupling commands, allowing for undo/redo functionality. It doesn’t necessarily involve communication between multiple objects.
  • Mediator Pattern: Defines an object that encapsulates how a set of objects interact. It promotes loose coupling by centralizing communication between objects. While it can be used in conjunction with the Command Pattern, its primary purpose is different.

Command vs. Observer

Command and Observer patterns address how receivers subscribe and receive requests, but their primary roles vary.

  • Command Pattern: Encapsulates commands and decouples senders from receivers. It’s suitable for scenarios where actions need to be executed without sender-receiver dependencies.
  • Observer Pattern: Defines a one-to-many dependency between objects. When one object (the subject) changes state, all its dependents (observers) are notified and updated automatically. While both patterns support decoupling, the Observer Pattern is more about keeping objects in sync with a changing subject.

Command and Memento

Can be used together for “undo” functionality, with Commands for operations and Mementos for saving object states.

Command and Prototype

Can help save copies of Commands into history.

Command and Visitor

Can be seen as a powerful version of the Command pattern, allowing objects to execute operations over various objects of different classes.

Dall-E generated image with the following concept: Singularity depicted by an abstraction of a cosmic nexus

Conclusion

In this article, we’ve delved into the Command Design Pattern and its Python implementation, covering its components, interactions with other design patterns, and real-world uses like multi-level undo/redo.

The Command Pattern offers valuable decoupling and encapsulation, enhancing software robustness and flexibility. Whether it’s a text editor, graphic design tool, or any system needing precise command management, the Command Pattern proves its prowess.

Hope you enjoyed the Command pattern exploration 🙌 Happy coding! 👨‍💻

Next on the Series 🚀

Read More 📜

The Series 🧭

References

  1. Design Patterns: Elements of Reusable Object-Oriented Software (Book)
  2. refactoring.guru Command
  3. Head First Design Patterns (Book)
  4. Command Method — Python Design Patterns
  5. Design Patterns and Video Games
  6. Chapter 11. Python Design Patterns II

--

--

Amir Lavasani

I delve into machine learning 🤖 and software architecture 🏰 to enhance my expertise while sharing insights with Medium readers. 📃