6.6. Command¶
EN: Command
PL: Polecenie
Type: object
6.6.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

6.6.2. 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()
6.6.3. 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 = 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