Паттерн «Команда» в Python
Паттерн Команда — это поведенческий паттерн проектирования, который инкапсулирует запрос как объект, тем самым позволяя параметризовать клиентов различными запросами. Проще говоря,паттерн Команда оборачивает операции (такие как открытие файлов, сохранение данных) в независимые объекты,позволяя вам обрабатывать операции так же, как вы обрабатываете данные.
Аналогия из повседневной жизни
Представьте процесс заказа в ресторане:
- Посетитель(клиент) не должен знать, как повар готовит блюдо
- Официант(инициатор) принимает заказ, но не готовит сам
- Заказ(объект команды) содержит информацию о конкретном блюде
- Повар(получатель) выполняет фактические операции приготовления на основе заказа
Этот паттерн с чётким разделением обязанностей — реальное проявление паттерна «Команда».
Зачем нужен паттерн «Команда»?
Ограничения традиционного подхода
В традиционном программировании мы обычно напрямую вызываем методы объекта:
Пример
def turn_on(self):
print("The light is on")
def turn_off(self):
print("The light is off")
# Прямой вызов
light = Light()
light.turn_on()
light.turn_off()
У этого подхода есть несколько проблем:
- Сильная связанность: вызывающему необходимо знать конкретный интерфейс получателя
- Сложно расширять: добавление новых возможностей требует изменения существующего кода
- Нет поддержки отмены/повтора: операции нельзя откатить после выполнения
Преимущества паттерна «Команда»

Паттерн «Команда» решает вышеуказанные проблемы, вводя промежуточный слой (объект команды), обеспечивая:
- Ослабление связанности: нет прямой зависимости между вызывающим и получателем
- Расширяемость: легко добавлять новые команды
- Поддержка отмены/повтора: можно записывать историю операций
- Поддержка очереди: команды можно помещать в очередь для отложенного выполнения
Основные компоненты паттерна «Команда»
Базовая структура
Паттерн «Команда» включает четыре основные роли:
| Роль | Ответственность | Пример |
|---|---|---|
| Command(Интерфейс команды) | Объявляет интерфейс для выполнения операций | execute(), undo() |
| ConcreteCommand(Конкретная команда) | Реализует интерфейс команды и связывает получателя | LightOnCommand, LightOffCommand |
| Receiver(Получатель) | Знает, как выполнять операции | Light, TV |
| Invoker(Инициатор) | Хранит и выполняет команды | RemoteControl |
Полный пример кода
Базовая реализация
Давайте разберём паттерн «Команда» на примере управления освещением в умном доме:
Пример
from typing import List
# 1. Интерфейс команды
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
# 2. Получатель — устройство освещения
class Light:
def turn_on(self):
print("💡 Свет включён")
def turn_off(self):
print("💡 Свет выключен")
# 3. Конкретная команда — команда включения света
class LightOnCommand(Command):
def __init__(self, light: Light):
self.light = light
def execute(self):
self.light.turn_on()
def undo(self):
self.light.turn_off()
# 4. Конкретная команда - Команда выключения света
class LightOffCommand(Command):
def __init__(self, light: Light):
self.light = light
def execute(self):
self.light.turn_off()
def undo(self):
self.light.turn_on()
# 5. Инициатор - Пульт управления
class RemoteControl:
def __init__(self):
self.command = None
self.history: List[Command] = []
def set_command(self, command: Command):
self.command = command
def press_button(self):
if self.command:
self.command.execute()
self.history.append(self.command)
def press_undo(self):
if self.history:
last_command = self.history.pop()
last_command.undo()
# Клиентский код
if __name__ == "__main__":
# Создать устройство
living_room_light = Light()
# Создать команды
light_on = LightOnCommand(living_room_light)
light_off = LightOffCommand(living_room_light)
# Создать пульт дистанционного управления
remote = RemoteControl()
# Тест включения света
print("=== Тест включения света ===")
remote.set_command(light_on)
remote.press_button()
# Тест выключения света
print("\n=== Тест выключения света ===")
remote.set_command(light_off)
remote.press_button()
# Тест отмены
print("\n=== Тест операции отмены ===")
remote.press_undo() # Отмена выключения света, должна включить свет
remote.press_undo() # Отмена включения света, должна выключить свет
Результат выполнения:

Пример продвинутого приложения
Универсальный пульт дистанционного управления, поддерживающий несколько устройств
Давайте расширим предыдущий пример и создадим универсальный пульт дистанционного управления, поддерживающий несколько устройств:
Пример
class TV:
def __init__(self, location: str):
self.location = location
self.is_on = False
self.volume = 50
def turn_on(self):
self.is_on = True
print(f"📺 {self.location} телевизор включён")
def turn_off(self):
self.is_on = False
print(f"📺 {self.location} телевизор выключен")
def set_volume(self, volume: int):
self.volume = volume
print(f"📺 {self.location} громкость телевизора установлена на {volume}")
# Команды, связанные с телевизором
class TVOnCommand(Command):
def __init__(self, tv: TV):
self.tv = tv
self.previous_volume = 50
def execute(self):
self.previous_volume = self.tv.volume
self.tv.turn_on()
def undo(self):
self.tv.turn_off()
self.tv.volume = self.previous_volume
class TVVolumeUpCommand(Command):
def __init__(self, tv: TV):
self.tv = tv
self.previous_volume = 50
def execute(self):
self.previous_volume = self.tv.volume
self.tv.set_volume(min(100, self.tv.volume + 10))
def undo(self):
self.tv.set_volume(self.previous_volume)
# Макрокоманда — выполнение нескольких команд одним нажатием
class MacroCommand(Command):
def __init__(self, commands: List[Command]):
self.commands = commands
def execute(self):
for command in self.commands:
command.execute()
def undo(self):
# Отмена в обратном порядке
for command in reversed(self.commands):
command.undo()
# Улучшенный пульт дистанционного управления
class AdvancedRemoteControl:
def __init__(self, slot_count: int = 4):
self.on_commands: List[Command] = [None] * slot_count
self.off_commands: List[Command] = [None] * slot_count
self.history: List[Command] = []
def set_command(self, slot: int, on_command: Command, off_command: Command):
self.on_commands[slot] = on_command
self.off_commands[slot] = off_command
def press_on_button(self, slot: int):
if self.on_commands[slot]:
self.on_commands[slot].execute()
self.history.append(self.on_commands[slot])
def press_off_button(self, slot: int):
if self.off_commands[slot]:
self.off_commands[slot].execute()
self.history.append(self.off_commands[slot])
def press_undo(self):
if self.history:
last_command = self.history.pop()
last_command.undo()
# Тест расширенного пульта дистанционного управления
def test_advanced_remote():
print("=== Тест универсального пульта дистанционного управления ===")
# Создать устройства
living_room_light = Light()
bedroom_tv = TV("Спальня")
# Создать команды
light_on = LightOnCommand(living_room_light)
light_off = LightOffCommand(living_room_light)
tv_on = TVOnCommand(bedroom_tv)
tv_volume_up = TVVolumeUpCommand(bedroom_tv)
# Создать макрокоманду - режим кино
cinema_mode = MacroCommand([light_off, tv_on, tv_volume_up])
# Настроить пульт дистанционного управления
remote = AdvancedRemoteControl()
remote.set_command(0, light_on, light_off) # Слот 0: управление светом
remote.set_command(1, tv_on, TVOnCommand(bedroom_tv)) # Слот 1: управление телевизором
remote.set_command(2, cinema_mode, light_on) # Слот 2: режим кино
# Проверить различные функции
print("\n1. Включить свет:")
remote.press_on_button(0)
print("\n2. Включить режим кинотеатра:")
remote.press_on_button(2)
print("\n3. Отменить операцию:")
remote.press_undo()
if __name__ == "__main__":
test_advanced_remote()
Параметры и опции паттерна «Команда»
Расширение интерфейса команды
В практических приложениях интерфейс команды может быть расширен дополнительной функциональностью по мере необходимости:
Пример
@abstractmethod
def execute(self):
"""Выполнить команду"""
pass
@abstractmethod
def undo(self):
"""Отменить команду"""
pass
@abstractmethod
def redo(self):
"""Команда повтора"""
pass
@abstractmethod
def can_execute(self) -> bool:
"""Проверяет, может ли команда быть выполнена"""
pass
@abstractmethod
def get_description(self) -> str:
"""Возвращает описание команды"""
pass
Очередь команд и отложенное выполнение
Паттерн «Команда» естественно поддерживает очереди команд, что очень полезно во многих сценариях:
Пример
def __init__(self):
self.queue: List[Command] = []
def add_command(self, command: Command):
self.queue.append(command)
def process_commands(self):
while self.queue:
command = self.queue.pop(0)
if command.can_execute():
command.execute()
def clear(self):
self.queue.clear()
Практические упражнения
Упражнение 1: Реализуйте систему управления кондиционером
Попробуйте реализовать паттерн «Команда» для системы управления кондиционером:
Пример
class AirConditioner:
def __init__(self):
self.temperature = 26
self.is_on = False
def turn_on(self):
# Реализуйте включение кондиционера
pass
def turn_off(self):
# Реализуйте выключение кондиционера
pass
def set_temperature(self, temp: int):
# Реализовать установку температуры
pass
# Реализовать классы команд, связанные с кондиционером
# AirConditionerOnCommand
# AirConditionerOffCommand
# TemperatureUpCommand
# TemperatureDownCommand
Упражнение 2: реализовать функциональность отмены для текстового редактора
Создайте простой текстовый редактор, поддерживающий ввод текста и операции отмены:
Пример
def __init__(self):
self.content = ""
def add_text(self, text: str):
# Реализовать добавление текста
pass
def delete_text(self, length: int):
# Реализовать удаление текста
pass
# Реализовать команды редактирования текста
# AddTextCommand
# DeleteTextCommand
ЧАВО
Вопрос: для каких сценариев подходит паттерн «Команда»?
A:Паттерн «Команда» особенно хорошо подходит для следующих сценариев:
- Когда операции нужно параметризовать (например, кнопки, привязанные к разным функциям)
- Когда требуется функциональность отмены/повтора
- Когда операции нужно помещать в очередь для отложенного выполнения
- Когда требуется записывать логи операций
- Когда требуется поддержка транзакционных операций
В: Каковы недостатки паттерна «Команда»?
A:Основные недостатки включают:
- Он может создать большое количество классов команд
- Он увеличивает сложность кода
- Он может быть слишком громоздким для простых операций
В: Как выбрать, использовать ли паттерн «Команда»?
A:Рассмотрите следующие пункты:
- Если вам нужны только простые вызовы методов, не используйте паттерн «Команда»
- Если требуется функциональность отмены/повтора, он настоятельно рекомендуется
- Если системе требуется высокая степень развязки, паттерн «Команда» — хороший выбор
- Если операции нужно ставить в очередь или логировать, паттерн «Команда» очень подходит
Итог
Паттерн «Команда» отделяет вызывающего от получателя, инкапсулируя операции как объекты, обеспечивая мощную расширяемость. Хотя он добавляет некоторую сложность кода, паттерн «Команда» является незаменимым паттерном проектирования в таких сценариях, как отмена/повтор, очереди операций и логирование.
Ключевые моменты:
- Паттерн «Команда» отделяет «что делать» от «кто это делает»
- Поддерживает такие продвинутые возможности, как отмена, повтор и очереди
- Легко расширяется новыми командами
- Широко используется в GUI-приложениях, транзакционных системах и разработке игр