Паттерн Команда
Часть раздела Объектно-ориентированное программирование путешествия по 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) идеально подходит для реализации систем отмены/повтора действий, очередей задач или записи макросов, когда необходимо работать с действиями как с объектами первого класса.
Задание
ЛегкоДавайте создадим командную систему текстового редактора, используя паттерн Команда (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: Выполнить все команды в очереди
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыБиблиотеки и импортВведение в ООПКлассы и объектыКлючевое слово thisМетодыПеременные экземпляраОсновы конструкторовИтоги — Простой калькулятор4Null-безопасность
Введение в Null-безопасностьNullable и Non-Nullable типыОператоры ? и !Ключевое слово Late и Null-безопасностьNull-aware операторыNull-безопасность в классахИтоги — Система профилей пользователей7Абстрактные классы и интерфейсы
Абстрактные классыАбстрактные методыИнтерфейсы в DartНеявные интерфейсыРеализация против наследованияМножественные интерфейсыИтоги — Калькулятор фигур10Коллекции и обобщения
Обзор List, Set, MapТипобезопасные коллекцииОбобщенные классыОбобщенные методыОграничения обобщенийIterable и IteratorИтоги: Обобщенное хранилище13Продвинутые концепции ООП
Композиция против наследованияМетоды расширенияВызываемые классыЗапечатанные классы (Dart 3)Записи (Dart 3)Паттерны и сопоставление (3.0)Перечисления с методами16Проект: Управление библиотекой
Обзор проектаКлассы Book и User2Конструкторы в Dart
Конструктор по умолчаниюИменованные конструкторыСписки инициализацииКонстантные конструкторыФабричные конструкторыПеренаправляющие конструкторыИтоги — Shape Builder5Инкапсуляция
Публичные и приватные членыСоглашение о префиксе _Приватность на уровне библиотекГеттеры и сеттеры: подробный разборСкрытие данныхИтоги — Записи о студентах8Mixins
Введение в MixinsСоздание MixinsИспользование нескольких MixinsКлючевое слово on в MixinsMixin и наследованиеMixin и интерфейсИтоги: Система животных11Специальные методы
Переопределение toString()Переопределение hashCode и ==Интерфейс ComparableМетод call()Переопределение noSuchMethodПовторение — Пользовательская коллекция14Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy3Свойства классов
Члены экземпляра и статические членыПоля Final и ConstПеременные LateСтатические методы и поляГеттеры и сеттерыИтоги — Менеджер банковского счета6Наследование
Основы наследованияКлючевое слово superПереопределение методовАннотация @overrideКлючевое слово final для классовКонструкторы и наследованиеИтоги: Иерархия сотрудников9Полиморфизм
Основы полиморфизмаПолиморфизм через интерфейсыПроверка типов во время выполненияОператоры is и asКлючевое слово covariantИтоги — Обработчик платежей12Асинхронное ООП
Futures и async/awaitОсновы StreamsКонтроллеры StreamАсинхронные конструкторыАсинхронность в методах классовИтоги — Data Fetcher15Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий