Паттерн Command. Реализация структуры
Содержание
- 1. Общие сведения. Назначение. Структура паттерна
- 2. Случаи применения
- 3. Реализация паттерна Command (по рисунку структуры) на C++. Упрощенный вариант (без сохранения состояния)
- 4. Отношения
- 5. Результаты
- 6. Применение шаблона для простых команд без аргументов. Пример на C++
- Связанные темы
1. Общие сведения. Назначение. Структура паттерна
Паттерн Command относится к паттернам поведения объектов. Паттерн также имеет другие названия: Action (действие), Transaction (транзакция).
Особенностью паттерна является то, что запрос на выполнение команды представляется в виде объекта, который передается в эту команду как параметр и соответствующим образом обрабатывается. Также запрос может быть поставлен в очередь и поддерживать отмену операций (сохраняется состояние команды).
Структура паттерна Command изображена на рисунке 1.
Рисунок 1. Структура паттерна Command
Участниками паттерна Command являются следующие.
- Command – интерфейс (абстрактный класс), предназначенный для выполнения конкретного метода (операции).
- ConcreteCommand – конкретная команда, которая:
- определяет связь между объектом-получателем Receiver и действием;
- реализует метод (операцию) Execute(), вызывающий соответствующий метод объекта Receiver.
- Client – клиент (клиентский класс), создающий объект класса ConcreteCommand и устанавливающий его получателя.
- Invoker – инициатор запроса, который обращается в команду для выполнения запроса.
- Receiver – класс-получатель, содержащий методы (операции), удовлетворяющие выполнению запроса. Получателем может выступать любой класс.
⇑
2. Случаи применения
Паттерн Command применяется в следующих случаях.
- Когда необходимо произвести параметризацию объектов заданным действием.
- В случае если созданный запрос нужно поставить в очередь и выполнить в разное время. Здесь допускается выполнение команды в другом процессе.
- Когда необходимо поддержать отмену операций. Здесь происходит сохранение состояния для отката действий, производимых командой.
- Если необходимо обеспечить ведение протокола изменений, которые можно выполнить повторно после аварийной остановки системы. Протокол может храниться и во внешней памяти.
- Когда необходимо структурировать систему из примитивных операций, сочетающихся в операции более высокого уровня.
⇑
3. Реализация паттерна Command (по рисунку структуры) на C++. Упрощенный вариант (без сохранения состояния)
// Паттерн Command (Мотивация) #include <iostream> using namespace std; // Класс - получатель class Receiver { public: void Action() { cout << "Receiver::Action()" << endl; } }; class Command abstract { public: // Операция - общее virtual void Execute() = 0; }; // В данном случае этот класс не обязателен, // он заменяется шаблонным классом SimpleCommand<Receiver> class ConcreteCommand : public Command { private: Receiver* receiver; public: // Конструктор ConcreteCommand(Receiver* _receiver) { receiver = _receiver; } // реализация интерфейса Command void Execute() override { receiver->Action(); } }; // Класс клиента - случай без шаблонного класса class Client { private: Receiver* receiver; // ссылка на Receiver public: // Конструктор - получает Receiver Client(Receiver* receiver) { // Создать того, кто вызывает this->receiver = receiver; } // Вызывающий метод - Invoker void InvokeCommand() { Command* invoker; invoker = new ConcreteCommand(receiver); invoker->Execute(); if (invoker) delete invoker; } }; // Шаблонный класс. Здесь вместо типа Receiver // можно подставлять любой другой тип (класс). template <class Receiver> class SimpleCommand : public Command { public: typedef void (Receiver::* Action)(); SimpleCommand(Receiver* r, Action a) : _receiver(r), _action(a) { } virtual void Execute() { (_receiver->*_action)(); } private: Action _action; Receiver* _receiver; }; void main() { // --------- Случай 1 - с шаблонным классом // Клиент (Client) Receiver* receiver = new Receiver; // Это тот кто вызывает (Invoker), // этот код можно поместить в метод некоторого класса. Command* aCommand = new SimpleCommand<Receiver>(receiver, &Receiver::Action); aCommand->Execute(); // Receiver->Action() if (aCommand != nullptr) delete aCommand; if (receiver != nullptr) delete receiver; // ------------------------------- // ----- Случай 2 - без шаблонного класса receiver = new Receiver; // Это есть класс-клиент Client client(receiver); client.InvokeCommand(); if (receiver) delete receiver; }
⇑
4. Отношения
Между участниками паттерна выделяются следующие отношения.
- Клиент создает конкретную команду (ConcreteCommand) в виде объекта и устанавливает для нее получателя (Receiver).
- Инициатор (Invoker) сохраняет объект ConcreteCommand.
- Инициатор отправляет запрос путём вызова Execute(). Если поддерживается отмена выполненных действий, перед вызовом Execute() в классе ConcreteCommand сохраняется информация о состоянии. Эта информация должна быть такой, чтобы можно было совершить откат выполненных действий.
- Объект ConcreteCommand вызывает методы получателя (Receiver) для выполнения запроса.
⇑
5. Результаты
Для паттерна Command можно выделить следующие результаты.
- Разрывается связь между объектом, инициировавшим вызов метода (операции), и объектом, имеющим информацию о том, как выполнить этот метод.
- Каждая команда – это объект, которым можно манипулировать или расширять его.
- Из простых команд могут быть образованы составные команды, которые могут быть описаны паттерном Composite (компоновщик).
- Новые команды добавляются легко, поскольку для этого не нужно изменять существующие классы. Каждая новая команда только добавляет новый класс.
⇑
6. Применение шаблона для простых команд без аргументов. Пример на C++
Если нужно реализовать простые команды, не требующие аргументов, можно сформировать шаблонный класс, типом которого будет получатель (Receiver).
Ниже представлен пример использования такого шаблонного класса. Создается шаблонный класс SimpleCommand<Receiver>, определяющий простую команду.
// Паттерн Command (Мотивация) #include <iostream> using namespace std; // Класс - получатель class Receiver { public: void Action() { cout << "Receiver::Action()" << endl; } }; class Command abstract { public: // Операция - общее virtual void Execute() = 0; }; // В данном случае этот класс не обязателен, // он заменяется шаблонным классом SimpleCommand<Receiver> class ConcreteCommand : public Command { private: Receiver* receiver; public: // Конструктор ConcreteCommand(Receiver* _receiver) { receiver = _receiver; } // реализация интерфейса Command void Execute() override { receiver->Action(); } }; // Шаблонный класс. Здесь вместо типа Receiver // можно подставлять любой другой тип (класс). template <class Receiver> class SimpleCommand : public Command { public: typedef void (Receiver::* Action)(); SimpleCommand(Receiver* r, Action a) : _receiver(r), _action(a) { } virtual void Execute() { (_receiver->*_action)(); } private: Action _action; Receiver* _receiver; }; void main() { // Клиент (Client) Receiver* receiver = new Receiver; // Это тот кто вызывает (Invoker), // этот код можно поместить в метод некоторого класса. Command* aCommand = new SimpleCommand<Receiver>(receiver, &Receiver::Action); aCommand->Execute(); // Receiver->Action() if (aCommand != nullptr) delete aCommand; if (receiver != nullptr) delete receiver; }
⇑
Связанные темы
- Паттерны поведения. Обзор
- Паттерн Chain of Responsibility. Общие сведения. Реализация на C++
- Паттерн Iterator. Особенности реализации на C++ для полиморфного контейнера и полиморфного итератора
- Паттерн State (Состояние). Реализация структуры на C++
⇑