Паттерн Команда
Часть раздела 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), очереди операций и логирование команд.
Задание
ЛегкоВ этом испытании вы реализуете базовый класс 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)}")В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыПараметр selfМетодыАтрибутыМетод-конструктор (__init__)Повторение — Простой калькулятор4Наследование
Базовое наследованиеФункция super()Переопределение методовМножественное наследованиеПорядок разрешения методовИтоги — Иерархия сотрудников7Специальные методы
Введение в магические методыПерегрузка операторовМагические методы контейнеровПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy5Полиморфизм
Снова о переопределении методовУтиная типизацияАбстрактные классы и методыПроектирование интерфейсовИтоги — Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияМиксиныСтатические методы и методы классаДекораторы классовМенеджеры контекста11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Свойства классов
Переменные экземпляра и классаДекораторы свойствПриватные атрибутыИтоги — Менеджер банковского счета6Инкапсуляция
Public, Protected, Private члены классаМодификаторы доступаСокрытие данныхДекораторы property: продвинутый уровеньИтоги: Система учета студентов12Проект: Управление библиотекой
Обзор проектаКлассы Book и User