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