Menu
Coddy logo textTech

Паттерн Команда

Часть раздела Object Oriented Programming путешествия по Python на Coddy — урок 49 из 64.

Паттерн «Команда» инкапсулирует запрос в виде объекта, позволяя ставить операции в очередь, логировать запросы и поддерживать функциональность отмены. Он отделяет объект, инициирующий операцию, от объекта, который её непосредственно выполняет.

Вот простые классы команд:

class Command:
    def execute(self):
        pass

class LightOnCommand(Command):
    def __init__(self, light):
        self.light = light
    
    def execute(self):
        self.light.turn_on()

class LightOffCommand(Command):
    def __init__(self, light):
        self.light = light
    
    def execute(self):
        self.light.turn_off()

Каждая команда инкапсулирует конкретную операцию над объектом-получателем.

Создайте получателя (receiver), который выполняет фактическую работу:

class Light:
    def turn_on(self):
        print("Light is on")
    
    def turn_off(self):
        print("Light is off")

Создайте инициатор (invoker), который выполняет команды:

class RemoteControl:
    def __init__(self):
        self.command = None
    
    def set_command(self, command):
        self.command = command
    
    def press_button(self):
        self.command.execute()

Используйте паттерн «Команда»:

# Создаем получателя
light = Light()

# Создаем команды
light_on = LightOnCommand(light)
light_off = LightOffCommand(light)

# Создаем инициатора
remote = RemoteControl()

# Выполняем различные команды
remote.set_command(light_on)
remote.press_button()

remote.set_command(light_off)
remote.press_button()

Добавьте поддержку операций отмены:

class UndoableCommand(Command):
    def undo(self):
        pass

class LightOnCommand(UndoableCommand):
    def __init__(self, light):
        self.light = light
    
    def execute(self):
        self.light.turn_on()
    
    def undo(self):
        self.light.turn_off()

class SmartRemote:
    def __init__(self):
        self.last_command = None
    
    def execute_command(self, command):
        command.execute()
        self.last_command = command
    
    def undo(self):
        if self.last_command:
            self.last_command.undo()

smart_remote = SmartRemote()
smart_remote.execute_command(LightOnCommand(light))
smart_remote.undo()  # Выключает свет

Вывод:

Light is on
Light is off
Light is on
Light is off

Ключевой момент: Паттерн Команда (Command) превращает запросы в объекты, которые можно сохранять, передавать и выполнять позже. Инициатору (invoker) не нужно знать, как именно выполняется операция — он просто вызывает execute() у объекта команды. Это позволяет реализовать такие функции, как отмена/повтор (undo/redo), очереди операций и логирование команд.

challenge icon

Задание

Легко

В этом испытании вы реализуете базовый класс Command в файле command.py — основной строительный блок паттерна Команда (Command Pattern). Это упражнение сосредоточено именно на инкапсуляции: приватном хранении данных и их безопасном предоставлении через свойство.

Изменяйте только command.py в соответствии с комментариями TODO. Комментарии TODO помогут вам:

  • Сохранить имя команды как приватный атрибут (_name)
  • Предоставить к нему доступ через свойство только для чтения (name)
  • Реализовать метод display_info(), который выводит имя команды

Примечание: Это испытание охватывает только базовый класс Command — отправитель (Invoker), получатель (Receiver) и функциональность отмены (undo) будут представлены в последующих уроках. Выполнение этого шага даст вам фундамент инкапсуляции, на котором строится весь паттерн.

Ваша реализация будет протестирована файлом driver.py, который проверяет:

  • Базовую функциональность и формат вывода
  • Граничные случаи (пустые входные данные, специальные символы, длинные имена)
  • Защиту свойства только для чтения (попытка выполнить obj.name = ... должна вызывать AttributeError)
  • Наличие приватного атрибута _name

Шпаргалка

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

Базовая структура команды:

class Command:
    def execute(self):
        pass

class LightOnCommand(Command):
    def __init__(self, light):
        self.light = light
    
    def execute(self):
        self.light.turn_on()

class LightOffCommand(Command):
    def __init__(self, light):
        self.light = light
    
    def execute(self):
        self.light.turn_off()

Создайте получателя (receiver), который выполняет фактическую работу:

class Light:
    def turn_on(self):
        print("Light is on")
    
    def turn_off(self):
        print("Light is off")

Создайте инициатор (invoker), который выполняет команды:

class RemoteControl:
    def __init__(self):
        self.command = None
    
    def set_command(self, command):
        self.command = command
    
    def press_button(self):
        self.command.execute()

Пример использования:

# Создание получателя
light = Light()

# Создание команд
light_on = LightOnCommand(light)
light_off = LightOffCommand(light)

# Создание инициатора
remote = RemoteControl()

# Выполнение различных команд
remote.set_command(light_on)
remote.press_button()

remote.set_command(light_off)
remote.press_button()

Добавление функциональности отмены:

class UndoableCommand(Command):
    def undo(self):
        pass

class LightOnCommand(UndoableCommand):
    def __init__(self, light):
        self.light = light
    
    def execute(self):
        self.light.turn_on()
    
    def undo(self):
        self.light.turn_off()

class SmartRemote:
    def __init__(self):
        self.last_command = None
    
    def execute_command(self, command):
        command.execute()
        self.last_command = command
    
    def undo(self):
        if self.last_command:
            self.last_command.undo()

Попробуйте сами

# Импорт класса Command из command.py
from command import Command

# Комплексный обработчик тестовых случаев
test_case = input()

if test_case == "basic_test":
    obj = Command("Test Name")
    obj.display_info()
elif test_case == "validation_test":
    obj = Command("Validation Test")
    print(f"Name: {obj.name}")
elif test_case == "empty_name_test":
    obj = Command("")
    obj.display_info()
    print(f"Empty name handled: {'Yes' if obj.name == '' else 'No'}")
elif test_case == "property_access_test":
    obj = Command("Property Test")
    original_name = obj.name
    try:
        # Это должно завершиться ошибкой, так как name является свойством только для чтения
        obj.name = "Modified Name"
        print("Property protection failed")
    except AttributeError:
        print("Property protected successfully")
    print(f"Name unchanged: {obj.name == original_name}")
elif test_case == "multiple_commands_test":
    commands = [
        Command("First Command"),
        Command("Second Command"),
        Command("Third Command")
    ]
    for cmd in commands:
        cmd.display_info()
elif test_case == "attribute_test":
    obj = Command("Attribute Test")
    has_private_name = hasattr(obj, "_name")
    print(f"Has _name attribute: {has_private_name}")
elif test_case == "special_chars_test":
    obj = Command("!@#$%^&*()_+{}[]|\\:;\"'<>,.?/")
    obj.display_info()
elif test_case == "long_name_test":
    long_name = "A" * 100
    obj = Command(long_name)
    obj.display_info()
    print(f"Name length: {len(obj.name)}")
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Object Oriented Programming