7.8. Command
EN: Command
PL: Polecenie
Type: object
The Command design pattern is a behavioral design pattern that turns a request into a stand-alone object that contains all information about the request. This transformation lets you pass requests as a method arguments, delay or queue a request's execution, and support undoable operations.
In Python, we can implement the Command pattern using classes. Here's a simple example:
First, we define a Command
class that declares an abstract execute
method:
class Command:
def execute(self):
pass
Then, we define a ConcreteCommand
class that implements the execute
method:
class ConcreteCommand(Command):
def __init__(self, receiver):
self._receiver = receiver
def execute(self):
self._receiver.action()
The Receiver
class has the actual business logic that should be performed:
class Receiver:
def action(self):
print("Receiver action is being performed.")
Finally, we have an Invoker
class that calls the command:
class Invoker:
def __init__(self, command):
self._command = command
def call(self):
self._command.execute()
receiver = Receiver()
command = ConcreteCommand(receiver)
invoker = Invoker(command)
invoker.call()
Receiver action is being performed.
In this example, the Invoker
calls the ConcreteCommand
, which performs an action
on the Receiver
.
7.8.1. Pattern
Receiver — The Object that will receive and execute the command
Invoker — Which will send the command to the receiver
Command Object — Itself, which implements an execute, or action method, and contains all required information
Client — The main application or module which is aware of the Receiver, Invoker and Commands
GUI Buttons, menus
Macro recording
Multi level undo/redo (See Tutorial)
Networking — send whole command objects across a network, even as a batch
Parallel processing or thread pools
Transactional behaviour — Rollback whole set of commands, or defer till later
Wizards
7.8.2. Problem
class Light:
def action(self, action: str):
if action == 'on':
print('Lights on')
elif action == 'off':
print('Lights off')
light = Light()
light.action('on')
Lights on
light.action('off')
Lights off
7.8.3. Solution
from typing import Protocol
class Command(Protocol):
def execute(self): ...
class Tasks:
commands: list[Command]
def __init__(self):
self.commands = []
def add(self, command: Command):
self.commands.append(command)
def run(self):
for command in self.commands:
command.execute()
class LightsOnCommand:
def execute(self):
print('Lights on')
class LightsOffCommand:
def execute(self):
print('Lights off')
task = Tasks()
task.add(LightsOnCommand())
task.add(LightsOffCommand())
task.add(LightsOnCommand())
task.add(LightsOffCommand())
task.add(LightsOnCommand())
task.add(LightsOffCommand())
task.run()
Lights on
Lights off
Lights on
Lights off
Lights on
Lights off
7.8.4. Case Study
Problem:
class Button:
label: str
def set_label(self, name):
self.label = name
def get_label(self):
return self.label
def click(self):
...
if __name__ == '__main__':
button = Button()
button.set_label('My Button')
button.click()
Solution:

Command pattern:
from abc import ABC, abstractmethod
from dataclasses import dataclass
class Command(ABC):
@abstractmethod
def execute(self) -> None:
pass
class Button:
label: str
command: Command
def __init__(self, command: Command):
self.command = command
def set_label(self, name):
self.label = name
def get_label(self):
return self.label
def click(self):
self.command.execute()
class CustomerService:
def add_customer(self) -> None:
print('Add customer')
@dataclass
class AddCustomerCommand(Command):
service: CustomerService
def execute(self) -> None:
self.service.add_customer()
if __name__ == '__main__':
service = CustomerService()
command = AddCustomerCommand(service)
button = Button(command)
button.click()
# Add customer
Composite commands (Macros):
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
class Command(ABC):
@abstractmethod
def execute(self) -> None:
pass
class ResizeCommand(Command):
def execute(self) -> None:
print('Resize')
class BlackAndWhiteCommand(Command):
def execute(self) -> None:
print('Black And White')
@dataclass
class CompositeCommand(Command):
commands: list[Command] = field(default_factory=list)
def add(self, command: Command) -> None:
self.commands.append(command)
def execute(self) -> None:
for command in self.commands:
command.execute()
if __name__ == '__main__':
composite = CompositeCommand()
composite.add(ResizeCommand())
composite.add(BlackAndWhiteCommand())
composite.execute()
# Resize
# Black And White
Undoable commands:
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
class Command(ABC):
@abstractmethod
def execute(self) -> None:
pass
class UndoableCommand(Command):
@abstractmethod
def unexecute(self) -> None:
pass
@dataclass
class History:
commands: list[UndoableCommand] = field(default_factory=list)
def push(self, command: UndoableCommand) -> None:
self.commands.append(command)
def pop(self):
return self.commands.pop()
def size(self) -> int:
return len(self.commands)
@dataclass
class HtmlDocument:
content: str = ''
def set_content(self, content):
self.content = content
def get_content(self):
return self.content
@dataclass
class BoldCommand(UndoableCommand):
document: HtmlDocument
history: History = field(default_factory=History)
previous_content: str | None = None
def unexecute(self) -> None:
self.document.set_content(self.previous_content)
def apply(self, content):
return f'<b>{content}</b>'
def execute(self) -> None:
current_content = self.document.get_content()
self.previous_content = current_content
self.document.set_content(self.apply(current_content))
self.history.push(self)
@dataclass
class UndoCommand(Command):
history: History
def execute(self) -> None:
if self.history.size() > 0:
self.history.pop().unexecute()
if __name__ == '__main__':
history = History()
document = HtmlDocument('Hello World')
# This should be onButtonClick or KeyboardShortcut
BoldCommand(document, history).execute()
print(document.get_content())
# <b>Hello World</b>
# This should be onButtonClick or KeyboardShortcut
UndoCommand(history).execute()
print(document.get_content())
# Hello World
7.8.5. Further Reading
7.8.6. Use Case - 1
from typing import Protocol
class Command(Protocol):
def execute(self): """Execute command"""
class MorseCode:
commands: list[Command]
def __init__(self):
self.commands = []
def add(self, command: Command):
self.commands.append(command)
def send(self):
print('Sending message:')
for command in self.commands:
command.execute()
print('STOP')
class A:
def execute(self):
print('.-', end=' ')
class B:
def execute(self):
print('-...', end=' ')
class C:
def execute(self):
print('-.-.', end=' ')
class D:
def execute(self):
print('-..', end=' ')
class E:
def execute(self):
print('.', end=' ')
class F:
def execute(self):
print('..-.', end=' ')
class G:
def execute(self):
print('--.', end=' ')
class H:
def execute(self):
print('....', end=' ')
class I:
def execute(self):
print('..', end=' ')
class J:
def execute(self):
print('.---', end=' ')
class K:
def execute(self):
print('-.-', end=' ')
class L:
def execute(self):
print('.-..', end=' ')
class M:
def execute(self):
print('--', end=' ')
class N:
def execute(self):
print('-.', end=' ')
class O:
def execute(self):
print('---', end=' ')
class P:
def execute(self):
print('.--.', end=' ')
class Q:
def execute(self):
print('--.-', end=' ')
class R:
def execute(self):
print('.-.', end=' ')
class S:
def execute(self):
print('...', end=' ')
class T:
def execute(self):
print('-', end=' ')
class U:
def execute(self):
print('..-', end=' ')
class V:
def execute(self):
print('...-', end=' ')
class W:
def execute(self):
print('.--', end=' ')
class X:
def execute(self):
print('-..-', end=' ')
class Y:
def execute(self):
print('-.--', end=' ')
class Z:
def execute(self):
print('--..', end=' ')
message = MorseCode()
message.add(S())
message.add(O())
message.add(S())
message.send()
Sending message:
... --- ... STOP