Menu
Coddy logo textTech

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

Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 94 из 110.

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

Паттерн включает три ключевых компонента: интерфейс Command (команда) с методом execute, Concrete Commands (конкретные команды), которые реализуют определенные действия, и Receiver (получатель), который выполняет фактическую работу. Необязательный Invoker (инициатор) запускает команды, не зная, что они делают:

abstract class Command {
  void execute();
}

class Light {
  void turnOn() => print('Light is ON');
  void turnOff() => print('Light is OFF');
}

class TurnOnCommand implements Command {
  final Light light;
  TurnOnCommand(this.light);

  @override
  void execute() => light.turnOn();
}

class TurnOffCommand implements Command {
  final Light light;
  TurnOffCommand(this.light);

  @override
  void execute() => light.turnOff();
}

class RemoteControl {
  Command? _command;

  void setCommand(Command command) {
    _command = command;
  }

  void pressButton() {
    _command?.execute();
  }
}

void main() {
  var light = Light();
  var remote = RemoteControl();

  remote.setCommand(TurnOnCommand(light));
  remote.pressButton();  // Light is ON

  remote.setCommand(TurnOffCommand(light));
  remote.pressButton();  // Light is OFF
}

RemoteControl ничего не знает об источниках света — он просто выполняет любую команду, которую ему передают. Такая развязка позволяет легко добавлять новые команды без изменения инициатора (invoker). Паттерн «Команда» (Command pattern) идеально подходит для реализации систем отмены/повтора действий, очередей задач или записи макросов, когда необходимо работать с действиями как с объектами первого класса.

challenge icon

Задание

Легко

Давайте создадим командную систему текстового редактора, используя паттерн Команда (Command)! Вы создадите систему, которая инкапсулирует текстовые операции в виде объектов команд, позволяя редактору выполнять различные действия, не зная деталей каждой операции. Это классический вариант использования паттерна Команда — представьте его как систему отмены/повтора действий (undo/redo), где каждое действие хранится как объект.

Вы организуете свой код в двух файлах:

  • commands.dart: Создайте здесь свою командную систему. Начните с абстрактного класса Command, который объявляет метод execute(). Затем создайте класс TextDocument (получатель), который содержит поле String content (изначально пустое) и имеет три метода:
    • append(String text) — добавляет текст в конец содержимого
    • clear() — устанавливает содержимое в пустую строку
    • show() — выводит на печать Document: [content]
    Теперь реализуйте три конкретные команды, которые работают с TextDocument:
    • AppendCommand — принимает документ и текст в конструкторе и добавляет текст при выполнении
    • ClearCommand — принимает документ в конструкторе и очищает его при выполнении
    • ShowCommand — принимает документ в конструкторе и показывает его содержимое при выполнении
    Наконец, создайте класс TextEditor (инициатор), который хранит список команд. У него должен быть метод addCommand(Command command) для постановки команд в очередь и метод runAll(), который последовательно выполняет все команды из очереди.
  • main.dart: Импортируйте файл с командами и продемонстрируйте работу паттерна Команда. Создайте TextDocument и TextEditor. Добавьте в очередь следующие команды по порядку: добавить Hello, добавить World, показать документ, добавить !, снова показать, очистить документ и показать в последний раз. Затем запустите все команды из очереди.

Обратите внимание, что TextEditor ничего не знает о добавлении текста, очистке или отображении — он просто выполняет любые команды, которые ему дают. Это разделение (decoupling) и есть суть паттерна Команда!

Ожидаемый результат:

Document: Hello World
Document: Hello World!
Document: 

Шпаргалка

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

Паттерн включает в себя три ключевых компонента:

  • Интерфейс Command с методом execute
  • Concrete Commands (Конкретные команды), которые реализуют определенные действия
  • Receiver (Получатель), который выполняет фактическую работу
  • Invoker (Инициатор) (необязательно), который запускает команды, не зная, что они делают

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

abstract class Command {
  void execute();
}

class Receiver {
  void action() => print('Action performed');
}

class ConcreteCommand implements Command {
  final Receiver receiver;
  ConcreteCommand(this.receiver);

  @override
  void execute() => receiver.action();
}

class Invoker {
  Command? _command;

  void setCommand(Command command) {
    _command = command;
  }

  void trigger() {
    _command?.execute();
  }
}

Пример с системой управления освещением:

class Light {
  void turnOn() => print('Light is ON');
  void turnOff() => print('Light is OFF');
}

class TurnOnCommand implements Command {
  final Light light;
  TurnOnCommand(this.light);

  @override
  void execute() => light.turnOn();
}

class RemoteControl {
  Command? _command;

  void setCommand(Command command) {
    _command = command;
  }

  void pressButton() {
    _command?.execute();
  }
}

void main() {
  var light = Light();
  var remote = RemoteControl();

  remote.setCommand(TurnOnCommand(light));
  remote.pressButton();  // Light is ON
}

Инициатор (invoker) ничего не знает о получателе (receiver) — он просто выполняет любую команду, которую ему передают. Такая развязка (decoupling) позволяет легко добавлять новые команды без изменения инициатора.

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

import 'commands.dart';

void main() {
  // TODO: Создайте экземпляр TextDocument

  // TODO: Создайте экземпляр TextEditor

  // TODO: Добавьте следующие команды в очередь по порядку:
  // 1. Добавить "Hello"
  // 2. Добавить " World"
  // 3. Показать документ
  // 4. Добавить "!"
  // 5. Снова показать документ
  // 6. Очистить документ
  // 7. Показать документ в последний раз

  // TODO: Выполнить все команды в очереди
}
quiz iconПроверьте себя

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

Все уроки раздела Объектно-ориентированное программирование