Паттерн 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++
- Паттерн Command. Реалізація структури на C++
- Паттерн Iterator. Особливості реалізації на C++ для поліморфного контейнера та поліморфного ітератора
⇑