Паттерн Command (команда)

Назначение паттерна Command

Используйте паттерн Command если

  • Система управляется событиями. При появлении такого события (запроса) необходимо выполнить определенную последовательность действий.
  • Необходимо параметризировать объекты выполняемым действием, ставить запросы в очередь или поддерживать операции отмены (undo) и повтора (redo) действий.
  • Нужен объектно-ориентированный аналог функции обратного вызова в процедурном программировании.

Пример событийно-управляемой системы – приложение с пользовательским интерфейсом. При выборе некоторого пункта меню пользователем вырабатывается запрос на выполнение определенного действия (например, открытия файла).

Описание паттерна Command

Паттерн Command преобразовывает запрос на выполнение действия в отдельный объект-команду. Такая инкапсуляция позволяет передавать эти действия другим функциям и объектам в качестве параметра, приказывая им выполнить запрошенную операцию. Команда – это объект, поэтому над ней допустимы любые операции, что и над объектом.

Интерфейс командного объекта определяется абстрактным базовым классом Command и в самом простом случае имеет единственный метод execute(). Производные классы определяют получателя запроса (указатель на объект-получатель) и необходимую для выполнения операцию (метод этого объекта). Метод execute() подклассов Command просто вызывает нужную операцию получателя.

В паттерне Command может быть до трех участников:

  • Клиент, создающий экземпляр командного объекта.
  • Инициатор запроса, использующий командный объект.
  • Получатель запроса.

UML-диаграмма классов паттерна Command

Сначала клиент создает объект ConcreteCommand, конфигурируя его получателем запроса. Этот объект также доступен инициатору. Инициатор использует его при отправке запроса, вызывая метод execute(). Этот алгоритм напоминает работу функции обратного вызова в процедурном программировании – функция регистрируется, чтобы быть вызванной позднее.

Паттерн Command отделяет объект, инициирующий операцию, от объекта, который знает, как ее выполнить. Единственное, что должен знать инициатор, это как отправить команду. Это придает системе гибкость: позволяет осуществлять динамическую замену команд, использовать сложные составные команды, осуществлять отмену операций.

Реализация паттерна Command

Рассмотрим реализацию паттерна Command на примере игры «Шахматы». Имитируем возможность выполнения следующих операций:

  • Создать новую игру.
  • Открыть существующую игру.
  • Сохранить игру.
  • Сделать очередной ход.
  • Отменить последний ход.
#include<iostream>
#include<vector>
#include<string>

class Game
{
  public:
    void create( ) {
      cout << "Create game " << endl;
    }     
    void open( string file ) {
      cout << "Open game from " << file << endl;
    }
    void save( string file ) {
      cout << "Save game in " << file << endl;
    }
    void make_move( string move ) {
      cout << "Make move " << move << endl;
    }
};

string getPlayerInput( string prompt ) {
  string input;
  cout << prompt;
  cin >> input;
  return input;
}

// Базовый класс
class Command 
{   
  public:
    virtual ~Command() {}
    virtual void execute() = 0;
  protected:
    Command( Game* p ): pgame( p) {}
    Game * pgame;       
};

class CreateGameCommand: public Command 
{
  public:
    CreateGameCommand( Game * p ) : Command( p) {}
    void execute() {
      pgame->create( );
    }
};

class OpenGameCommand: public Command 
{
  public:
    OpenGameCommand( Game * p ) : Command( p) {}
    void execute() {
      string file_name;
      file_name = getPlayerInput( "Enter file name:");
      pgame->open( file_name);
    }
};

class SaveGameCommand: public Command 
{
  public:
    SaveGameCommand( Game * p ) : Command( p) {}
    void execute( ) {
      string file_name;
      file_name = getPlayerInput( "Enter file name:");
      pgame->save( file_name);
    }
};

class MakeMoveCommand: public Command 
{
  public:
    MakeMoveCommand( Game * p) : Command( p) {}
    void execute() { 
      // Сохраним игру для возможного последующего отката
      pgame->save( "TEMP_FILE");
      string move;
      move = getPlayerInput( "Enter your move:");
      pgame->make_move( move);
    }
};

class UndoCommand: public Command 
{
  public:
    UndoCommand( Game * p ) : Command( p) {}
    void execute() { 
      // Восстановим игру из временного файла
      pgame->open( "TEMP_FILE");
    }
};


int main()
{
  Game game;
  // Имитация действий игрока
  vector<Command*> v;
  // Создаем новую игру 
  v.push_back( new CreateGameCommand( &game));  
  // Делаем несколько ходов
  v.push_back( new MakeMoveCommand( &game));
  v.push_back( new MakeMoveCommand( &game));
  // Последний ход отменяем
  v.push_back( new UndoCommand( &game));
  // Сохраняем игру
  v.push_back( new SaveGameCommand( &game));

  for (size_t i=0; i<v.size(); ++i)
    v[i]->execute();

  for (size_t i=0; i<v.size(); ++i)
    delete v[i];

  return 0;
}

Вывод программы:

Create game 
Save game in TEMP_FILE
Enter your move: E2-E4
Make move E2-E4
Save game in TEMP_FILE
Enter your move: D2-D3
Make move D2-D3
Open game from TEMP_FILE
Enter file name: game1.sav
Save game in game1.sav

Результаты применения паттерна Command

Достоинства паттерна Command

  • Придает системе гибкость, отделяя инициатора запроса от его получателя.

results matching ""

    No results matching ""