8.6. Command

8.6.1. Rationale

  • EN: Command

  • PL: Polecenie

  • Type: object

8.6.2. Use Cases

  • 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

  • Source 1

8.6.3. 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()

8.6.4. Design

  • 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

../_images/designpatterns-command-gof.png

8.6.5. Implementation

../_images/designpatterns-command-usecase.png

Command pattern:

from abc import ABCMeta, abstractmethod
from dataclasses import dataclass


class Command(metaclass=ABCMeta):
    @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 ABCMeta, abstractmethod
from dataclasses import dataclass, field


class Command(metaclass=ABCMeta):
    @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 ABCMeta, abstractmethod
from dataclasses import dataclass, field


class Command(metaclass=ABCMeta):
    @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

8.6.6. Assignments

Todo

Create assignments