Patterns. Паттерн Command. Реалізація схеми

Паттерн Command. Реалізація схеми

1. Загальні відомості. Призначення. Структура паттерну

Паттерн Command належить до паттернів поведінки об’єктів. Паттерн ще має інші назви: Action (дія), Transaction (транзакція).

Особливістю паттерну є те, що запит на виконання команди представляється у вигляді об’єкту, який в цю команду передається як параметр і відповідним чином обробляється. Також запит може бути поставлений в чергу а також підтримувати відміну операцій (зберігається стан команди).

Структура паттерну Command зображена на рисунку 1.

Паттерн Command. Структура

Рисунок 1. Структура паттерну Command

Учасниками паттерну Command є наступні.

  1. Command – інтерфейс (абстрактний клас), що призначений для виконання конкретного методу (операції);
  2. ConcreteCommand – конкретна команда, яка:
  • визначає зв’язок між об’єктом-отримувачем Receiver і дією;
  • реалізує метод (операцію) Execute(), який викликає відповідний метод об’єкту Receiver.
  1. Client – клієнт (клієнтський клас), який створює об’єкт класу ConcreteCommand і встановлює його отримувача.
  2. Invoker – ініціатор запиту, який звертається до команди для виконання запиту.
  3. Receiver – клас-отримувач, що містить методи (операції), які задовільняють виконання запиту. Отримувачем може виступати будь-який клас.

 

2. Випадки застосування

Паттерн Command застосовується у наступних випадках.

  1. Коли потрібно здійснит параметризацію об’єктів заданою дією.
  2. У випадку, якщо створений запит потрібно поставити в чергу і виконати в різний час. Тут допускається виконання команди в іншому процесі.
  3. Коли потрібно підтримати відміну операцій. Тут відбувається збереження стану для відкату дій, що виконались командою.
  4. Якщо потрібно забезпечити ведення протоколу змін, які потім можна виконати повторно після аварійної зупинки системи. Протокол може зберігатись і в зовнішній пам’яті.
  5. Коли необхідно структурувати систему з примітивних операцій, які поєднуються в операції більш високого рівня.

 

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. Відношення

Між учасниками паттерну виділяються такі відношення.

  1. Клієнт створює конкретну команду (ConcreteCommand) у вигляді об’єкту і встановлює для неї отримувача (Receiver).
  2. Ініціатор (Invoker) зберігає об’єкт ConcreteCommand.
  3. Ініціатор відправляє запит шляхом виклику Execute(). Якщо підтримується відміна виконаних дій, то перед викликом Execute() в класі ConcreteCommand зберігається інформація про стан. Ця інформація повинна бути такою, щоб можна було здійснити відкат виконаних дій.
  4. Об’єкт ConcreteCommand викликає методи отримувача (Receiver) для виконання запиту.

 

5. Результати

Для паттерну Command можна виділити наступні результати.

  1. Розривається зв’язок між об’єктом, який ініціював виклик методу (операції), та об’єктом, який має інформацію про те як виконати цей метод.
  2. Кожна команда – це об’єкт, яким можна маніпулювати чи розширювати його.
  3. З простих команд можуть бути утворені складені команди, які можуть бути описані паттерном Composite (Компонувальник).
  4. Нові команди додаються легко, оскільки для цього не потрібно змінювати вже існуючі класи. Кожна нова команда тільки додає новий клас.

 

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;
}