Команда (шаблон проектирования)
Команда | |
---|---|
Command | |
Тип | поведенческий |
Назначение | для обработки команды в виде объекта |
Родственные шаблоны | Компоновщик, Хранитель, Прототип, Одиночка |
Описан в Design Patterns | Да |
Команда (англ. Command) — поведенческий шаблон проектирования, используемый при объектно-ориентированном программировании, представляющий действие. Объект команды заключает в себе само действие и его параметры.
Цель
[править | править код]Создание структуры, в которой класс-отправитель и класс-получатель не зависят друг от друга напрямую. Организация обратного вызова к классу, который включает в себя класс-отправитель.
Описание
[править | править код]В объектно-ориентированном программировании шаблон проектирования Команда является поведенческим шаблоном, в котором объект используется для инкапсуляции всей информации, необходимой для выполнения действия или вызова события в более позднее время. Эта информация включает в себя имя метода, объект, который является владельцем метода и значения параметров метода.
Четыре термина всегда связаны с шаблоном Команда: команды (command), приёмник команд (receiver), вызывающий команды (invoker) и клиент (client). Объект Command знает о приёмнике и вызывает метод приёмника. Значения параметров приёмника сохраняются в команде. Вызывающий объект (invoker) знает, как выполнить команду и, возможно, делает учёт и запись выполненных команд. Вызывающий объект (invoker) ничего не знает о конкретной команде, он знает только об интерфейсе. Оба объекта (вызывающий объект и несколько объектов команд) принадлежат объекту клиента (client). Клиент решает, какие команды выполнить и когда. Чтобы выполнить команду он передает объект команды вызывающему объекту (invoker).
Использование командных объектов упрощает построение общих компонентов, которые необходимо делегировать или выполнять вызовы методов в любое время без необходимости знать методы класса или параметров метода. Использование вызывающего объекта (invoker) позволяет вести учёт выполненных команд без необходимости знать клиенту об этой модели учёта (такой учёт может пригодиться, например, для реализации отмены и повтора команд).
Применение
[править | править код]Шаблон Команда может быть полезен в следующих случаях.
- Кнопки пользовательского интерфейса и пункты меню
В Swing и Borland Delphi Action (действие) является объектом команды. В дополнение к способности выполнить нужную команду, Action может иметь связанную с ним иконку, сочетание клавиш, текст всплывающей подсказки и так далее. Кнопка на панели инструментов или пункт меню могут быть полностью инициализированы с использованием только объекта Action.
- Запись макросов
Если все действия пользователя представлены в виде объектов команды, программа может записать последовательность действий, просто сохраняя список командных объектов в том порядке, в котором они выполняются. Затем она может «воспроизвести» одни и те же действия, выполняя те же объекты команд в той же последовательности.
- Многоуровневая отмена операций (Undo)
Если все действия пользователя в программе реализованы в виде командных объектов, программа может сохранить стек последних выполненных команд. Когда пользователь хочет отменить команду, программа просто выталкивает последний объект команды и выполняет его метод undo().
- Сети
Можно отправить объекты команд по сети для выполнения на другой машине, например действие игрока в компьютерной игре.
- Индикаторы выполнения
Предположим, что программа имеет последовательность команд, которые она выполняет по порядку. Если каждый объект команды имеет метод getEstimatedDuration() (получить оценочную длительность), программа может легко оценить общую продолжительность процесса. Она может показать индикатор выполнения, который отражает, насколько близка программа к завершению всех задач.
- Пулы потоков
Типичный класс пула потоков общего назначения может иметь метод addTask(), который добавляет рабочий элемент к внутренней очереди заданий ожидающих своего выполнения. Он поддерживает пул потоков, которые выполняют команды из очереди. Элементы в очереди являются объектами команд. Как правило, эти объекты реализуют общий интерфейс, такой как java.lang.Runnable, что позволяет пулу потоков запустить команды на выполнение, даже если он сам был написан без каких-либо знаний о конкретных задачах, для которых он будет использоваться.
- Транзакции
Аналогично операции «отмена» система управления базами данных (СУБД) или установщик программного обеспечения может хранить список операций, которые были или будут выполнены. Если одна из них закончится неудачей, то все остальные могут быть отменены или быть отброшены (обычно называется откат). Например, если две связанные между собой таблицы базы данных должны быть обновлены, а второе обновление терпит неудачу, то система может откатить транзакцию, чтобы первая таблица не содержала недопустимую ссылку.
- Мастера
Часто мастер (мастер установки или любой другой) представляет несколько страниц конфигурации для одного действия, которое происходит только тогда, когда пользователь нажимает на кнопку «Готово» на последней странице. В этих случаях, естественный способ отделить код пользовательского интерфейса от кода приложения является реализация мастера с помощью объекта команд. Объект команда создается при первом отображении мастера. Каждая страница мастера сохраняет свои изменения в объекте команды, поэтому объект заполняется по мере перехода пользователя. Кнопка «Готово» просто запускает метод execute() на выполнение.
Примеры
[править | править код]Пример на C++
[править | править код]#include <iostream> #include <vector> #include <string> using namespace std; class Document { vector<string> data; public: Document() { data.reserve(100); // at least for 100 lines } void Insert( int line, const string & str ) { if ( line <= data.size() ) data.insert( data.begin() + line, str ); else cout << "Error!" << endl; } void Remove( int line ) { if( !( line>data.size() ) ) data.erase( data.begin() + line ); else cout << "Error!" << endl; } string & operator [] ( int x ) { return data[x]; } void Show() { for( int i = 0; i<data.size(); ++i ) { cout << i + 1 << ". " << data[i] << endl; } } }; class Command { protected: Document * doc; public: virtual ~Command() {} virtual void Execute() = 0; virtual void unExecute() = 0; void setDocument( Document * _doc ) { doc = _doc; } }; class InsertCommand : public Command { int line; string str; public: InsertCommand( int _line, const string & _str ): line( _line ), str( _str ) {} void Execute() { doc->Insert( line, str ); } void unExecute() { doc->Remove( line ); } }; class Invoker { vector<Command*> DoneCommands; Document doc; Command* command; public: void Insert( int line, string str ) { command = new InsertCommand( line, str ); command->setDocument( &doc ); command->Execute(); DoneCommands.push_back( command ); } void Undo() { 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!!! delete command; } } void Show() { doc.Show(); } }; int main() { char s = '1'; int line, line_b; string str; Invoker inv; 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; } }
Пример на Python
[править | править код]from abc import ABCMeta, abstractmethod class Troop: """ Receiver - объект военного отряда """ def move(self, direction: str) -> None: """ Начать движение в определенном направлении """ print('Отряд начал движение {}'.format(direction)) def stop(self) -> None: """ Остановиться """ print('Отряд остановился') class Command(metaclass=ABCMeta): """ Базовый класс для всех команд """ @abstractmethod def execute(self) -> None: """ Приступить к выполнению команды """ pass @abstractmethod def unexecute(self) -> None: """ Отменить выполнение команды """ pass class AttackCommand(Command): """ Команда для выполнения атаки """ def __init__(self, troop: Troop) -> None: """ Constructor. :param troop: отряд, с которым ассоциируется команда """ self.troop = troop def execute(self) -> None: self.troop.move('вперед') def unexecute(self) -> None: self.troop.stop() class RetreatCommand(Command): """ Команда для выполнения отступления """ def __init__(self, troop: Troop) -> None: """ Constructor. :param troop: отряд, с которым ассоциируется команда """ self.troop = troop def execute(self) -> None: self.troop.move('назад') def unexecute(self) -> None: self.troop.stop() class TroopInterface: """ Invoker - интерфейс, через который можно отдать команды определенному отряду """ def __init__(self, attack: AttackCommand, retreat: RetreatCommand) -> None: """ Constructor. :param attack: команда для выполнения атаки :param retreat: команда для выполнения отступления """ self.attack_command = attack self.retreat_command = retreat self.current_command = None # команда, выполняющаяся в данный момент def attack(self) -> None: self.current_command = self.attack_command self.attack_command.execute() def retreat(self) -> None: self.current_command = self.retreat_command self.retreat_command.execute() def stop(self) -> None: if self.current_command: self.current_command.unexecute() self.current_command = None else: print('Отряд не может остановиться, так как не двигается') if __name__ == '__main__': troop = Troop() interface = TroopInterface(AttackCommand(troop), RetreatCommand(troop)) interface.attack() interface.stop() interface.retreat() interface.stop()
Пример на PHP5
[править | править код]<?php /** * Абстрактый класс "команды" * @abstract */ abstract class Command { public abstract function Execute(); public abstract function UnExecute(); } /** * Класс конкретной "команды" */ class CalculatorCommand extends Command { /** * Текущая операция команды * * @var string */ public $operator; /** * Текущий операнд * * @var mixed */ public $operand; /** * Класс, для которого предназначена команда * * @var object of class Calculator */ public $calculator; /** * Конструктор * * @param object $calculator * @param string $operator * @param mixed $operand */ public function __construct($calculator, $operator, $operand) { $this->calculator = $calculator; $this->operator = $operator; $this->operand = $operand; } /** * Переопределенная функция parent::Execute() */ public function Execute() { $this->calculator->Operation($this->operator, $this->operand); } /** * Переопределенная функция parent::UnExecute() */ public function UnExecute() { $this->calculator->Operation($this->Undo($this->operator), $this->operand); } /** * Какое действие нужно отменить? * * @private * @param string $operator * @return string */ private function Undo($operator) { //каждому произведенному действию найти обратное switch($operator) { case '+': $undo = '-'; break; case '-': $undo = '+'; break; case '*': $undo = '/'; break; case '/': $undo = '*'; break; default : $undo = ' '; break; } return $undo; } } /** * Класс получатель и исполнитель "команд" */ class Calculator { /** * Текущий результат выполнения команд * * @private * @var int */ private $curr = 0; public function Operation($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)"); } } /** * Класс, вызывающий команды */ class User { /** * Этот класс будет получать команды на исполнение * * @private * @var object of class Calculator */ private $calculator; /** * Массив операций * * @private * @var array */ private $commands = array(); /** * Текущая команда в массиве операций * * @private * @var int */ private $current = 0; public function __construct() { //создать экземпляр класса, который будет исполнять команды $this->calculator = new Calculator(); } /** * Функция возврата отмененных команд * * @param int $levels количество возвращаемых операций */ public function Redo($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 количество отменяемых операций */ public function Undo($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 */ public function Compute($operator, $operand) { // Создаем команду операции и выполняем её $command = new CalculatorCommand($this->calculator, $operator, $operand); $command->Execute(); // Добавляем операцию к массиву операций и увеличиваем счетчик текущей операции $this->commands[]=$command; $this->current++; } } $user = new User(); // Произвольные команды $user->Compute('+', 100); $user->Compute('-', 50); $user->Compute('*', 10); $user->Compute('/', 2); // Отменяем 4 команды $user->Undo(4); // Вернём 3 отменённые команды. $user->Redo(3);
Пример на Java
[править | править код]Для того чтобы реализовать соответствие названий операций к действию, операции над лампой (switch on, switch off) вынесены в инстанс классов SwitchOnCommand
и SwitchOffCommand
, оба класса реализуют интерфейс Command
.
import java.util.HashMap; /** The Command interface */ interface Command { void execute(); } /** The Invoker class */ class Switch { private final HashMap<String, Command> commandMap = new HashMap<>(); public void register(String commandName, Command command) { commandMap.put(commandName, command); } public void execute(String commandName) { Command command = commandMap.get(commandName); if (command == null) { throw new IllegalStateException("no command registered for " + commandName); } command.execute(); } } /** The Receiver class */ class Light { public void turnOn() { System.out.println("The light is on"); } public void turnOff() { System.out.println("The light is off"); } } /** The Command for turning on the light - ConcreteCommand #1 */ class SwitchOnCommand implements Command { private final Light light; public SwitchOnCommand(Light light) { this.light = light; } @Override // Command public void execute() { light.turnOn(); } } /** The Command for turning off the light - ConcreteCommand #2 */ class SwitchOffCommand implements Command { private final Light light; public SwitchOffCommand(Light light) { this.light = light; } @Override // Command public void execute() { light.turnOff(); } } public class CommandDemo { public static void main(final String[] arguments) { Light lamp = new Light(); Command switchOn = new SwitchOnCommand(lamp); Command switchOff = new SwitchOffCommand(lamp); Switch mySwitch = new Switch(); mySwitch.register("on", switchOn); mySwitch.register("off", switchOff); mySwitch.execute("on"); mySwitch.execute("off"); } }
С использованием функционального интерфейса
Начиная с Java 8, не требуется обязательно создавать классы SwitchOnCommand
и SwitchOffCommand
вместо этого мы можем использовать оператор ::
как показано в следующем примере
public class CommandDemo { public static void main(final String[] arguments) { Light lamp = new Light(); Command switchOn = lamp::turnOn; Command switchOff = lamp::turnOff; Switch mySwitch = new Switch(); mySwitch.register("on", switchOn); mySwitch.register("off", switchOff); mySwitch.execute("on"); mySwitch.execute("off"); } }
Пример на Swift 5
[править | править код]protocol Command { func execute() } // Invoker class Switch { var status: String? var action: Command? func register(_ command: Command) { self.action = command } func execute() { self.action?.execute() } } // Receiver class Light { func turnOn() { print("The light is ON") } func turnOff() { print("The light is OFF") } } class SwitchOnCommand: Command { private var light: Light init(_ light: Light) { self.light = light } func execute() { light.turnOn() } } class SwitchOffCommand: Command { private var light: Light init(_ light: Light) { self.light = light } func execute() { light.turnOff() } } // USE let invoker = Switch() let reciver = Light() let commaneNO = SwitchOnCommand(Light()) invoker.register(commaneNO) invoker.execute() let commandOff = SwitchOffCommand(Light()) invoker.register(commaneNO) invoker.execute()
Пример на Ruby
[править | править код]module EngineCommands # Abstract class 'Command' class Command def execute end end # Receiver class Engine attr_reader :state def initialize rpm @state, @rpm = false, rpm if rpm.is_a? Integer end def turnOn; @state = true; end def turnOff; @state = false; end end # ConcreteCommand1 class CommandTurnOn < Command def initialize engine @engine = engine if engine.is_a? Engine end def execute @engine.turnOn end end # ConcreteCommand2 class CommandTurnOff < Command def initialize engine @engine = engine if engine.is_a? Engine end def execute @engine.turnOff end end # Invoker class Invoker def initialize @commands = Hash.new end def registerCommand commandName, command @commands[commandName] = command if command.is_a? Command and @commands[commandName].is_a? NilClass end def executeCommand commandName @command = @commands[commandName] unless @command.is_a? NilClass @command.execute else raise TypeError.new end end end end # Client module Client include EngineCommands invoker = Invoker.new engine = Engine.new(250) commandTurnOn = CommandTurnOn.new(engine) commandTurnOff = CommandTurnOff.new(engine) invoker.registerCommand "turnOn", commandTurnOn invoker.registerCommand "turnOff", commandTurnOff puts "\t Engine State before using command's: #{ engine.state } " # => Engine State before using command's: false puts "\t Engine State after using command 'turnOn': #{ invoker.executeCommand "turnOn" } " # => Engine State after using command 'turnOn': true puts "\t Engine State after use command 'turnOff': #{ invoker.executeCommand "turnOff" } " # => Engine State after use command 'turnOff': false end
Ссылки
[править | править код]- Паттерн проектирования Command (Команда) — назначение, описание, реализация на C++, достоинства и недостатки
В статье не хватает ссылок на источники (см. рекомендации по поиску). |