Паттерн «Команда» в Python

Паттерн Команда — это поведенческий паттерн проектирования, который инкапсулирует запрос как объект, тем самым позволяя параметризовать клиентов различными запросами. Проще говоря,паттерн Команда оборачивает операции (такие как открытие файлов, сохранение данных) в независимые объекты,позволяя вам обрабатывать операции так же, как вы обрабатываете данные.

Аналогия из повседневной жизни

Представьте процесс заказа в ресторане:

  • Посетитель(клиент) не должен знать, как повар готовит блюдо
  • Официант(инициатор) принимает заказ, но не готовит сам
  • Заказ(объект команды) содержит информацию о конкретном блюде
  • Повар(получатель) выполняет фактические операции приготовления на основе заказа

Этот паттерн с чётким разделением обязанностей — реальное проявление паттерна «Команда».


Зачем нужен паттерн «Команда»?

Ограничения традиционного подхода

В традиционном программировании мы обычно напрямую вызываем методы объекта:

Пример

class Light:
    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()

У этого подхода есть несколько проблем:

  1. Сильная связанность: вызывающему необходимо знать конкретный интерфейс получателя
  2. Сложно расширять: добавление новых возможностей требует изменения существующего кода
  3. Нет поддержки отмены/повтора: операции нельзя откатить после выполнения

Преимущества паттерна «Команда»

Паттерн «Команда» решает вышеуказанные проблемы, вводя промежуточный слой (объект команды), обеспечивая:

  • Ослабление связанности: нет прямой зависимости между вызывающим и получателем
  • Расширяемость: легко добавлять новые команды
  • Поддержка отмены/повтора: можно записывать историю операций
  • Поддержка очереди: команды можно помещать в очередь для отложенного выполнения

Основные компоненты паттерна «Команда»

Базовая структура

Паттерн «Команда» включает четыре основные роли:

Роль Ответственность Пример
Command(Интерфейс команды) Объявляет интерфейс для выполнения операций execute(), undo()
ConcreteCommand(Конкретная команда) Реализует интерфейс команды и связывает получателя LightOnCommand, LightOffCommand
Receiver(Получатель) Знает, как выполнять операции Light, TV
Invoker(Инициатор) Хранит и выполняет команды RemoteControl

Полный пример кода

Базовая реализация

Давайте разберём паттерн «Команда» на примере управления освещением в умном доме:

Пример

from abc import ABC, abstractmethod
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()

Параметры и опции паттерна «Команда»

Расширение интерфейса команды

В практических приложениях интерфейс команды может быть расширен дополнительной функциональностью по мере необходимости:

Пример

class AdvancedCommand(ABC):
    @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

Очередь команд и отложенное выполнение

Паттерн «Команда» естественно поддерживает очереди команд, что очень полезно во многих сценариях:

Пример

class CommandQueue:
    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: реализовать функциональность отмены для текстового редактора

Создайте простой текстовый редактор, поддерживающий ввод текста и операции отмены:

Пример

class TextEditor:
    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-приложениях, транзакционных системах и разработке игр
Другие расширения