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

Название и классификация паттерна

Команда — паттерн поведения объектов.

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

Инкапсулирует запрос как объект, позволяя тем самым задавать параметры клиентов для обработки соответствующих запросов, ставить запросы в очередь или протоколировать их, а также поддерживать отмену операций.

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

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

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

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

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

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

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

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

Структура паттерна Command

Структура паттерна Command показана на рис. 63.

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

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

-“

receiver->action();

Рис. 63. UML-диаграмма паттерна Команда

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

Участники

Command — Команда: объявляет интерфейс для выполнения операции.

ConcreteCommand — конкретная Команда: определяет связь между объектом-получателем Receiver и действием; реализует операцию Execute путем вызова соответствующих операций объекта Receiver.

Client — клиент: создает объект класса ConcreteCommand и устанавливает его получателя.

Invoker — инициатор: обращается к команде для выполнения запроса.

Receiver — получатель: располагает информацией о способах выполнения операций, необходимых для удовлетворения запроса. В роли получателя может выступать любой класс.

Отношения

Клиент создает объект ConcreteCommand и устанавливает для него получателя.

Инициатор Invoker сохраняет объект ConcreteCommand.

Инициатор отправляет запрос, вызывая операцию команды Execute. Если поддерживается отмена выполненных действий, то ConcreteCommand перед вызовом Execute сохраняет информацию о состоянии, достаточную для выполнения отката.

aReceiver aClient

aCommand

aninvoker

Г"| newCommand(aReceiver)

StoreCommand(aCommand)

У

W

т

ExecuteQ

-

ActionQ Д-

т

т

Рис. 64. UML-диаграмма последовательностей паттерна Команда

l

T

Объект ConcreteCommand вызывает операции получателя для выполнения запроса.

На рис. 64 видно, как Command разрывает связь между инициатором и получателем (а также запросом, который должен выполнить последний).

Результаты применения паттерна Команда

Команда разрывает связь между объектом, инициирующим операцию, и объектом, имеющим информацию о том, как ее выполнить.

Команды — это самые настоящие объекты. Допускается манипулировать ими и расширять их точно так же, как в случае с любыми другими объектами.

Из простых команд можно собирать составные. В общем случае составные команды описываются паттерном Компоновщик.

Добавлять новые команды легко, поскольку никакие существующие классы изменять не нужно.

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

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

  • • создать новую игру;
  • • открыть существующую игру;
  • • сохранить игру;
  • • сделать очередной ход;
  • • отменить последний ход.

#include

#include

#include

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() 0 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 F1LE"); 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 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; iexecute();

for (size_t i=0; i

return 0;

}

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

Create game

Save game in TEMPFILE 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: gamel.sav Save game in game 1 .sav

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

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

Родственные паттерны

Паттерн Компоновщик можно использовать для реализации макрокоманд.

Паттерн Хранитель иногда проектируется так, что сохраняет состояние команды, необходимое для отмены ее действия.

Команда, которую нужно копировать перед помещением в список истории, ведет себя, как Прототип.

 
< Пред   СОДЕРЖАНИЕ     След >