Команда (англ.Command) — поведенческийшаблон проектирования, используемый при объектно-ориентированном программировании, представляющий действие. Объект команды заключает в себе само действие и его параметры.
Создание структуры, в которой класс-отправитель и класс-получатель не зависят друг от друга напрямую. Организация обратного вызова к классу, который включает в себя класс-отправитель.
Описание
В объектно-ориентированном программировании шаблон проектирования Команда является поведенческим шаблоном, в котором объект используется для инкапсуляции всей информации, необходимой для выполнения действия или вызова события в более позднее время. Эта информация включает в себя имя метода, объект, который является владельцем метода и значения параметров метода.
Четыре термина всегда связаны с шаблоном Команда: команды (command), приёмник команд (receiver), вызывающий команды (invoker) и клиент (client). Объект Command знает о приёмнике и вызывает метод приёмника. Значения параметров приёмника сохраняются в команде. Вызывающий объект (invoker) знает, как выполнить команду и, возможно, делает учёт и запись выполненных команд. Вызывающий объект (invoker) ничего не знает о конкретной команде, он знает только об интерфейсе. Оба объекта (вызывающий объект и несколько объектов команд) принадлежат объекту клиента (client). Клиент решает, какие команды выполнить и когда. Чтобы выполнить команду он передает объект команды вызывающему объекту (invoker).
Использование командных объектов упрощает построение общих компонентов, которые необходимо делегировать или выполнять вызовы методов в любое время без необходимости знать методы класса или параметров метода. Использование вызывающего объекта (invoker) позволяет вести учёт выполненных команд без необходимости знать клиенту об этой модели учёта (такой учёт может пригодиться, например, для реализации отмены и повтора команд).
Применение
Шаблон Команда может быть полезен в следующих случаях.
Кнопки пользовательского интерфейса и пункты меню
В Swing и Borland DelphiAction (действие) является объектом команды. В дополнение к способности выполнить нужную команду, Action может иметь связанную с ним иконку, сочетание клавиш, текст всплывающей подсказки и так далее. Кнопка на панели инструментов или пункт меню могут быть полностью инициализированы с использованием только объекта Action.
Запись макросов
Если все действия пользователя представлены в виде объектов команды, программа может записать последовательность действий, просто сохраняя список командных объектов в том порядке, в котором они выполняются. Затем она может «воспроизвести» одни и те же действия, выполняя те же объекты команд в той же последовательности.
Многоуровневая отмена операций (Undo)
Если все действия пользователя в программе реализованы в виде командных объектов, программа может сохранить стек последних выполненных команд. Когда пользователь хочет отменить команду, программа просто выталкивает последний объект команды и выполняет его метод undo().
Сети
Можно отправить объекты команд по сети для выполнения на другой машине, например действие игрока в компьютерной игре.
Индикаторы выполнения
Предположим, что программа имеет последовательность команд, которые она выполняет по порядку. Если каждый объект команды имеет метод getEstimatedDuration() (получить оценочную длительность), программа может легко оценить общую продолжительность процесса. Она может показать индикатор выполнения, который отражает, насколько близка программа к завершению всех задач.
Пулы потоков
Типичный класс пула потоков общего назначения может иметь метод addTask(), который добавляет рабочий элемент к внутренней очереди заданий ожидающих своего выполнения. Он поддерживает пул потоков, которые выполняют команды из очереди. Элементы в очереди являются объектами команд. Как правило, эти объекты реализуют общий интерфейс, такой как java.lang.Runnable, что позволяет пулу потоков запустить команды на выполнение, даже если он сам был написан без каких-либо знаний о конкретных задачах, для которых он будет использоваться.
Транзакции
Аналогично операции «отмена» система управления базами данных (СУБД) или установщик программного обеспечения может хранить список операций, которые были или будут выполнены. Если одна из них закончится неудачей, то все остальные могут быть отменены или быть отброшены (обычно называется откат). Например, если две связанные между собой таблицы базы данных должны быть обновлены, а второе обновление терпит неудачу, то система может откатить транзакцию, чтобы первая таблица не содержала недопустимую ссылку.
Мастера
Часто мастер (мастер установки или любой другой) представляет несколько страниц конфигурации для одного действия, которое происходит только тогда, когда пользователь нажимает на кнопку «Готово» на последней странице. В этих случаях, естественный способ отделить код пользовательского интерфейса от кода приложения является реализация мастера с помощью объекта команд. Объект команда создается при первом отображении мастера. Каждая страница мастера сохраняет свои изменения в объекте команды, поэтому объект заполняется по мере перехода пользователя. Кнопка «Готово» просто запускает метод execute() на выполнение.
#include<iostream>#include<vector>#include<string>usingnamespacestd;classDocument{vector<string>data;public:Document(){data.reserve(100);// at least for 100 lines}voidInsert(intline,conststring&str){if(line<=data.size())data.insert(data.begin()+line,str);elsecout<<"Error!"<<endl;}voidRemove(intline){if(!(line>data.size()))data.erase(data.begin()+line);elsecout<<"Error!"<<endl;}string&operator[](intx){returndata[x];}voidShow(){for(inti=0;i<data.size();++i){cout<<i+1<<". "<<data[i]<<endl;}}};classCommand{protected:Document*doc;public:virtual~Command(){}virtualvoidExecute()=0;virtualvoidunExecute()=0;voidsetDocument(Document*_doc){doc=_doc;}};classInsertCommand:publicCommand{intline;stringstr;public:InsertCommand(int_line,conststring&_str):line(_line),str(_str){}voidExecute(){doc->Insert(line,str);}voidunExecute(){doc->Remove(line);}};classInvoker{vector<Command*>DoneCommands;Documentdoc;Command*command;public:voidInsert(intline,stringstr){command=newInsertCommand(line,str);command->setDocument(&doc);command->Execute();DoneCommands.push_back(command);}voidUndo(){if(DoneCommands.size()==0){cout<<"There is nothing to undo!"<<endl;}else{command=DoneCommands.back();DoneCommands.pop_back();command->unExecute();// Don't forget to delete command!!!deletecommand;}}voidShow(){doc.Show();}};intmain(){chars='1';intline,line_b;stringstr;Invokerinv;while(s!='e'){cout<<"What to do: \n1.Add a line\n2.Undo last command"<<endl;cin>>s;switch(s){case'1':cout<<"What line to insert: ";cin>>line;--line;cout<<"What to insert: ";cin>>str;inv.Insert(line,str);break;case'2':inv.Undo();break;}cout<<"$$$DOCUMENT$$$"<<endl;inv.Show();cout<<"$$$DOCUMENT$$$"<<endl;}}
fromabcimportABCMeta,abstractmethodclassTroop:""" Receiver - объект военного отряда """defmove(self,direction:str)->None:""" Начать движение в определенном направлении """print('Отряд начал движение {}'.format(direction))defstop(self)->None:""" Остановиться """print('Отряд остановился')classCommand(metaclass=ABCMeta):""" Базовый класс для всех команд """@abstractmethoddefexecute(self)->None:""" Приступить к выполнению команды """pass@abstractmethoddefunexecute(self)->None:""" Отменить выполнение команды """passclassAttackCommand(Command):""" Команда для выполнения атаки """def__init__(self,troop:Troop)->None:""" Constructor. :param troop: отряд, с которым ассоциируется команда """self.troop=troopdefexecute(self)->None:self.troop.move('вперед')defunexecute(self)->None:self.troop.stop()classRetreatCommand(Command):""" Команда для выполнения отступления """def__init__(self,troop:Troop)->None:""" Constructor. :param troop: отряд, с которым ассоциируется команда """self.troop=troopdefexecute(self)->None:self.troop.move('назад')defunexecute(self)->None:self.troop.stop()classTroopInterface:""" Invoker - интерфейс, через который можно отдать команды определенному отряду """def__init__(self,attack:AttackCommand,retreat:RetreatCommand)->None:""" Constructor. :param attack: команда для выполнения атаки :param retreat: команда для выполнения отступления """self.attack_command=attackself.retreat_command=retreatself.current_command=None# команда, выполняющаяся в данный моментdefattack(self)->None:self.current_command=self.attack_commandself.attack_command.execute()defretreat(self)->None:self.current_command=self.retreat_commandself.retreat_command.execute()defstop(self)->None:ifself.current_command:self.current_command.unexecute()self.current_command=Noneelse:print('Отряд не может остановиться, так как не двигается')if__name__=='__main__':troop=Troop()interface=TroopInterface(AttackCommand(troop),RetreatCommand(troop))interface.attack()interface.stop()interface.retreat()interface.stop()
<?php/** * Абстрактый класс "команды" * @abstract */abstractclassCommand{publicabstractfunctionExecute();publicabstractfunctionUnExecute();}/** * Класс конкретной "команды" */classCalculatorCommandextendsCommand{/** * Текущая операция команды * * @var string */public$operator;/** * Текущий операнд * * @var mixed */public$operand;/** * Класс, для которого предназначена команда * * @var object of class Calculator */public$calculator;/** * Конструктор * * @param object $calculator * @param string $operator * @param mixed $operand */publicfunction__construct($calculator,$operator,$operand){$this->calculator=$calculator;$this->operator=$operator;$this->operand=$operand;}/** * Переопределенная функция parent::Execute() */publicfunctionExecute(){$this->calculator->Operation($this->operator,$this->operand);}/** * Переопределенная функция parent::UnExecute() */publicfunctionUnExecute(){$this->calculator->Operation($this->Undo($this->operator),$this->operand);}/** * Какое действие нужно отменить? * * @private * @param string $operator * @return string */privatefunctionUndo($operator){//каждому произведенному действию найти обратноеswitch($operator){case'+':$undo='-';break;case'-':$undo='+';break;case'*':$undo='/';break;case'/':$undo='*';break;default:$undo=' ';break;}return$undo;}}/** * Класс получатель и исполнитель "команд" */classCalculator{/** * Текущий результат выполнения команд * * @private * @var int */private$curr=0;publicfunctionOperation($operator,$operand){//выбрать оператора для вычисления результатаswitch($operator){case'+':$this->curr+=$operand;break;case'-':$this->curr-=$operand;break;case'*':$this->curr*=$operand;break;case'/':$this->curr/=$operand;break;}print("Текущий результат = $this->curr (после выполнения $operator c $operand)");}}/** * Класс, вызывающий команды */classUser{/** * Этот класс будет получать команды на исполнение * * @private * @var object of class Calculator */private$calculator;/** * Массив операций * * @private * @var array */private$commands=array();/** * Текущая команда в массиве операций * * @private * @var int */private$current=0;publicfunction__construct(){//создать экземпляр класса, который будет исполнять команды$this->calculator=newCalculator();}/** * Функция возврата отмененных команд * * @param int $levels количество возвращаемых операций */publicfunctionRedo($levels){print("\n---- Повторить $levels операций ");// Делаем возврат операцийfor($i=0;$i<$levels;$i++)if($this->current<count($this->commands)-1)$this->commands[$this->current++]->Execute();}/** * Функция отмены команд * * @param int $levels количество отменяемых операций */publicfunctionUndo($levels){print("\n---- Отменить $levels операций ");// Делаем отмену операцийfor($i=0;$i<$levels;$i++)if($this->current>0)$this->commands[--$this->current]->UnExecute();}/** * Функция выполнения команд * * @param string $operator * @param mixed $operand */publicfunctionCompute($operator,$operand){// Создаем команду операции и выполняем её$command=newCalculatorCommand($this->calculator,$operator,$operand);$command->Execute();// Добавляем операцию к массиву операций и увеличиваем счетчик текущей операции$this->commands[]=$command;$this->current++;}}$user=newUser();// Произвольные команды$user->Compute('+',100);$user->Compute('-',50);$user->Compute('*',10);$user->Compute('/',2);// Отменяем 4 команды$user->Undo(4);// Вернём 3 отменённые команды.$user->Redo(3);
Для того чтобы реализовать соответствие названий операций к действию, операции над лампой (switch on, switch off) вынесены в инстанс классов SwitchOnCommand и SwitchOffCommand, оба класса реализуют интерфейс Command.
importjava.util.HashMap;/** The Command interface */interfaceCommand{voidexecute();}/** The Invoker class */classSwitch{privatefinalHashMap<String,Command>commandMap=newHashMap<>();publicvoidregister(StringcommandName,Commandcommand){commandMap.put(commandName,command);}publicvoidexecute(StringcommandName){Commandcommand=commandMap.get(commandName);if(command==null){thrownewIllegalStateException("no command registered for "+commandName);}command.execute();}}/** The Receiver class */classLight{publicvoidturnOn(){System.out.println("The light is on");}publicvoidturnOff(){System.out.println("The light is off");}}/** The Command for turning on the light - ConcreteCommand #1 */classSwitchOnCommandimplementsCommand{privatefinalLightlight;publicSwitchOnCommand(Lightlight){this.light=light;}@Override// Commandpublicvoidexecute(){light.turnOn();}}/** The Command for turning off the light - ConcreteCommand #2 */classSwitchOffCommandimplementsCommand{privatefinalLightlight;publicSwitchOffCommand(Lightlight){this.light=light;}@Override// Commandpublicvoidexecute(){light.turnOff();}}publicclassCommandDemo{publicstaticvoidmain(finalString[]arguments){Lightlamp=newLight();CommandswitchOn=newSwitchOnCommand(lamp);CommandswitchOff=newSwitchOffCommand(lamp);SwitchmySwitch=newSwitch();mySwitch.register("on",switchOn);mySwitch.register("off",switchOff);mySwitch.execute("on");mySwitch.execute("off");}}
С использованием функционального интерфейса
Начиная с Java 8, не требуется обязательно создавать классы SwitchOnCommand и SwitchOffCommand вместо этого мы можем использовать оператор :: как показано в следующем примере
moduleEngineCommands# Abstract class 'Command'classCommanddefexecuteendend# ReceiverclassEngineattr_reader:statedefinitializerpm@state,@rpm=false,rpmifrpm.is_a?IntegerenddefturnOn;@state=true;enddefturnOff;@state=false;endend# ConcreteCommand1classCommandTurnOn<Commanddefinitializeengine@engine=engineifengine.is_a?Engineenddefexecute@engine.turnOnendend# ConcreteCommand2classCommandTurnOff<Commanddefinitializeengine@engine=engineifengine.is_a?Engineenddefexecute@engine.turnOffendend# InvokerclassInvokerdefinitialize@commands=Hash.newenddefregisterCommandcommandName,command@commands[commandName]=commandifcommand.is_a?Commandand@commands[commandName].is_a?NilClassenddefexecuteCommandcommandName@command=@commands[commandName]unless@command.is_a?NilClass@command.executeelseraiseTypeError.newendendendend# ClientmoduleClientincludeEngineCommandsinvoker=Invoker.newengine=Engine.new(250)commandTurnOn=CommandTurnOn.new(engine)commandTurnOff=CommandTurnOff.new(engine)invoker.registerCommand"turnOn",commandTurnOninvoker.registerCommand"turnOff",commandTurnOffputs"\t Engine State before using command's: #{engine.state} "# => Engine State before using command's: falseputs"\t Engine State after using command 'turnOn': #{invoker.executeCommand"turnOn"} "# => Engine State after using command 'turnOn': trueputs"\t Engine State after use command 'turnOff': #{invoker.executeCommand"turnOff"} "# => Engine State after use command 'turnOff': falseend