Четыре термина, всегда связанные с шаблоном команды, — это команда , приемник , вызывающий и клиент . Объект команды знает о приемнике и вызывает метод приемника. Значения параметров метода приемника хранятся в команде. Объект приемника для выполнения этих методов также хранится в объекте команды путем агрегации . Затем приемник выполняет работу, когда вызывается execute()метод в команде . Объект вызывающего знает, как выполнить команду, и, при необходимости, ведет учет выполнения команды. Вызывающий ничего не знает о конкретной команде, он знает только об интерфейсе команды . Объект(ы) вызывающего, объекты команд и объекты приемника хранятся в объекте клиента . Клиент решает, какие объекты приемника он назначает объектам команд, а какие команды он назначает вызывающему. Клиент решает, какие команды выполнять в каких точках. Чтобы выполнить команду, он передает объект команды объекту вызывающего.
Использование объектов команд упрощает создание общих компонентов, которым необходимо делегировать, упорядочивать или выполнять вызовы методов в выбранное ими время без необходимости знать класс метода или параметры метода. Использование объекта-вызывающего позволяет удобно выполнять учет выполнения команд, а также реализовывать различные режимы для команд, которые управляются объектом-вызывающим, без необходимости для клиента знать о существовании учета или режимов.
Шаблон проектирования Command [1]
является одним из двадцати трех известных шаблонов проектирования GoF , которые описывают, как решать повторяющиеся проблемы проектирования для разработки гибкого и повторно используемого объектно-ориентированного программного обеспечения, то есть объектов, которые проще внедрять, изменять, тестировать и повторно использовать.
Использование шаблона проектирования команд может решить следующие проблемы: [2]
Следует избегать привязки вызывающего запрос к конкретному запросу. То есть следует избегать жестко запрограммированных запросов.
Должна быть возможность настроить объект (вызывающий запрос) с помощью запроса.
Реализация (жесткое подключение) запроса непосредственно в класс негибка, поскольку она связывает класс с конкретным запросом во время компиляции, что делает невозможным указание запроса во время выполнения.
Использование шаблона проектирования команд описывает следующее решение:
Определите отдельные (командные) объекты, инкапсулирующие запрос.
Класс делегирует запрос объекту команды вместо того, чтобы напрямую реализовывать конкретный запрос.
Это позволяет настроить класс с объектом команды, который используется для выполнения запроса. Класс больше не связан с конкретным запросом и не имеет знаний (независим) о том, как выполняется запрос.
См. также класс UML и диаграмму последовательности ниже.
Структура
Диаграмма классов и последовательностей UML
В приведенной выше диаграмме классов UML класс не реализует запрос напрямую. Вместо этого ссылается на интерфейс для выполнения запроса ( ), что делает независимым от того, как выполняется запрос. Класс реализует интерфейс, выполняя действие над получателем ( ).InvokerInvokerCommandcommand.execute()InvokerCommand1Commandreceiver1.action1()
Диаграмма последовательности UML
показывает взаимодействия во время выполнения: Объект вызывает объект . вызывает объект , который выполняет запрос.Invokerexecute()Command1Command1action1()Receiver1
Диаграмма классов UML
Использует
Кнопки и пункты меню графического интерфейса
В программировании Swing и Borland DelphiAction объект является объектом команды. Помимо возможности выполнять нужную команду, Действие может иметь связанный с ним значок, сочетание клавиш, текст подсказки и т. д. Кнопка панели инструментов или компонент элемента меню могут быть полностью инициализированы с использованием только объекта Действия .
Если все действия пользователя представлены объектами команд, программа может записывать последовательность действий, просто сохраняя список объектов команд по мере их выполнения. Затем она может «воспроизводить» те же действия, выполняя те же объекты команд снова в последовательности. Если программа встраивает механизм сценариев, каждый объект команд может реализовать метод toScript() , и действия пользователя затем можно легко записывать как сценарии.
Используя такие языки, как Java, где код может передаваться/передаваться из одного места в другое с помощью URLClassloaders и Codebases, команды могут обеспечивать доставку нового поведения в удаленные места (EJB Command, Master Worker).
Если все действия пользователя в программе реализованы как объекты команд, программа может хранить стек последних выполненных команд. Когда пользователь хочет отменить команду, программа просто выталкивает последний объект команды и выполняет его метод undo() .
Нетворкинг
По сети можно отправлять целые объекты команд для выполнения на других машинах, например, действия игрока в компьютерных играх.
Параллельная обработка
Где команды записываются как задачи для общего ресурса и выполняются многими потоками параллельно (возможно, на удаленных машинах; этот вариант часто называют шаблоном Master/Worker)
Предположим, что программа имеет последовательность команд, которые она выполняет по порядку. Если каждый объект команды имеет метод getEstimatedDuration() , программа может легко оценить общую продолжительность. Она может показывать индикатор выполнения, который содержательно отражает, насколько программа близка к завершению всех задач.
Типичный класс пула потоков общего назначения может иметь публичный метод addTask() , который добавляет рабочий элемент во внутреннюю очередь задач, ожидающих выполнения. Он поддерживает пул потоков, которые выполняют команды из очереди. Элементы в очереди являются командными объектами. Обычно эти объекты реализуют общий интерфейс, такой как java.lang.Runnable , который позволяет пулу потоков выполнять команду, даже если сам класс пула потоков был написан без каких-либо знаний о конкретных задачах, для которых он будет использоваться.
Подобно отмене, ядро базы данных или установщик программного обеспечения может хранить список операций, которые были или будут выполнены. Если одна из них не удалась, все остальные могут быть отменены или отброшены (обычно это называется откатом ). Например, если две таблицы базы данных, ссылающиеся друг на друга, должны быть обновлены, а второе обновление не удалось, транзакцию можно откатить, так что первая таблица теперь не будет содержать недействительную ссылку.
Часто мастер представляет несколько страниц конфигурации для одного действия, которое происходит только тогда, когда пользователь нажимает кнопку «Готово» на последней странице. В этих случаях естественным способом отделить код пользовательского интерфейса от кода приложения является реализация мастера с использованием объекта команды. Объект команды создается при первом отображении мастера. Каждая страница мастера сохраняет свои изменения GUI в объекте команды, поэтому объект заполняется по мере продвижения пользователя. «Готово» просто запускает вызов execute () . Таким образом, класс команды будет работать.
Терминология
Терминология, используемая для описания реализаций шаблонов команд, не является последовательной и поэтому может быть запутанной. Это является результатом двусмысленности , использования синонимов и реализаций, которые могут скрыть исходный шаблон, выходя далеко за его пределы.
Неоднозначность.
Термин команда неоднозначен. Например, перемещение вверх, перемещение вверх может относиться к одной команде (перемещение вверх), которая должна быть выполнена дважды, или может относиться к двум командам, каждая из которых делает одно и то же (перемещение вверх). Если первая команда дважды добавляется в стек отмены, оба элемента в стеке ссылаются на один и тот же экземпляр команды. Это может быть уместно, когда команду всегда можно отменить одним и тем же способом (например, перемещение вниз). И «Банда четырех» , и пример Java ниже используют эту интерпретацию термина команда . С другой стороны, если последние команды добавляются в стек отмены, стек ссылается на два отдельных объекта. Это может быть уместно, когда каждый объект в стеке должен содержать информацию, которая позволяет отменить команду. Например, чтобы отменить команду удаления выделения , объект может содержать копию удаленного текста, чтобы его можно было повторно вставить, если команда удаления выделения должна быть отменена. Обратите внимание, что использование отдельного объекта для каждого вызова команды также является примером шаблона цепочки ответственности .
Термин выполнить также неоднозначен. Он может относиться к запуску кода, идентифицированного методом выполнения объекта команды . Однако в Microsoft Windows Presentation Foundation команда считается выполненной, когда вызывается метод выполнения команды , но это не обязательно означает, что код приложения был запущен. Это происходит только после некоторой дальнейшей обработки событий.
Клиент, Источник, Вызыватель : нажатая пользователем кнопка, кнопка на панели инструментов или пункт меню, сочетание клавиш.
Объект команды, объект маршрутизированной команды, объект действия : одноэлементный объект (например, есть только один CopyCommandобъект), который знает о сочетаниях клавиш, изображениях кнопок, тексте команды и т. д., связанных с командой. Объект источника или вызывающего вызывает метод execute или performAction объекта команды или действия. Объект команды/действия уведомляет соответствующие объекты источника/вызывающего, когда доступность команды/действия изменилась. Это позволяет кнопкам и пунктам меню становиться неактивными (серыми), когда команда/действие не может быть выполнена/исполнена.
Получатель, целевой объект : объект, который будет скопирован, вставлен, перемещен и т. д. Получатель владеет методом, который вызывается методом execute команды . Получатель обычно также является целевым объектом. Например, если получатель — это курсор и вызывается метод moveUp, то можно было бы ожидать, что курсор является целью действия moveUp. С другой стороны, если код определен самим объектом команды, целевой объект будет совершенно другим объектом.
Объект Command, аргументы маршрутизируемого события, объект event : объект, который передается от источника к объекту Command/Action, к объекту Target и к коду, который выполняет работу. Каждое нажатие кнопки или сочетание клавиш приводит к созданию нового объекта command/event. Некоторые реализации добавляют больше информации к объекту command/event по мере его передачи от одного объекта (например, CopyCommand) к другому (например, раздел документа). Другие реализации помещают объекты command/event в другие объекты event (например, ящик внутри большего ящика) по мере их перемещения по строке, чтобы избежать конфликтов имен. (См. также шаблон chain of liability .)
Handler, ExecutedRoutedEventHandler, method, function : фактический код, который выполняет копирование, вставку, перемещение и т. д. В некоторых реализациях код обработчика является частью объекта команды/действия. В других реализациях код является частью объекта приемника/целевого объекта, а в других реализациях код обработчика хранится отдельно от других объектов.
Диспетчер команд, диспетчер отмен, планировщик, очередь, диспетчер, вызывающий объект : объект, который помещает объекты команд/событий в стек отмены или стек повтора, или который удерживает объекты команд/событий до тех пор, пока другие объекты не будут готовы к их выполнению, или который направляет объекты команд/событий соответствующему объекту-получателю/целевому объекту или коду обработчика.
Реализации, выходящие далеко за рамки исходного шаблона команд.
Windows Presentation Foundation (WPF) от Microsoft представляет маршрутизируемые команды, которые объединяют шаблон команды с обработкой событий. В результате объект команды больше не содержит ссылку на целевой объект или ссылку на код приложения. Вместо этого вызов команды выполнения объекта команды приводит к так называемому исполняемому маршрутизируемому событию , которое во время туннелирования или всплытия события может столкнуться с так называемым объектом привязки , который идентифицирует цель и код приложения, который выполняется в этой точке.
Пример
Данная реализация C++14 основана на реализации, существовавшей до C++98 и описанной в книге.
#include <iostream> #include <память>class Command { public : // объявляет интерфейс для выполнения операции. virtual void execute () = 0 ; virtual ~ Command () = default ; protected : Command () = default ; };template < typename Receiver > class SimpleCommand : public Command { // ConcreteCommand public : typedef void ( Receiver ::* Action )(); // определяет привязку между объектом Receiver и действием. SimpleCommand ( std :: shared_ptr < Receiver > receiver_ , Action action_ ) : receiver ( receiver_ . get ()), action ( action_ ) { } SimpleCommand ( const SimpleCommand & ) = delete ; // правило трех const SimpleCommand & Operator = ( const SimpleCommand & ) = delete ; // реализует execute путем вызова соответствующих операций на Receiver. virtual void execute () { ( receiver ->* action )(); } private : Receiver * receiver ; Action action ; };class MyClass { // Receiver public : // знает, как выполнять операции, связанные с выполнением запроса. Любой класс может служить Receiver. void action () { std :: cout << "MyClass::action \n " ; } };int main () { // Умные указатели предотвращают утечки памяти. std :: shared_ptr < MyClass > receiver = std :: make_shared < MyClass > (); // ... std :: unique_ptr < Command > command = std :: make_unique < SimpleCommand < MyClass > > ( receiver , & MyClass :: action ); // ... command -> execute (); }
Первое опубликованное упоминание об использовании класса Command для реализации интерактивных систем, по-видимому, относится к статье Генри Либермана 1985 года. [4] Первое опубликованное описание (многоуровневого) механизма отмены-повтора, использующего класс Command с методами execute и undo , а также список истории, по-видимому, относится к первому (1988) изданию книги Бертрана Мейера «Конструирование объектно-ориентированного программного обеспечения» , [5], раздел 12.2.
Ссылки
^ Эрих Гамма; Ричард Хелм; Ральф Джонсон; Джон Влиссидес (1994). Шаблоны проектирования: элементы повторно используемого объектно-ориентированного программного обеспечения . Эддисон Уэсли. стр. 233 и далее. ISBN 0-201-63361-2.
^ "Шаблон проектирования Command - Проблема, Решение и Применимость". w3sDesign.com . Архивировано из оригинала 2020-09-23 . Получено 2017-08-12 .
^ "Шаблон проектирования Command - Структура и сотрудничество". w3sDesign.com . Получено 2017-08-12 .[ мертвая ссылка ]
^ Либерман, Генри (1985). «В системах меню есть больше, чем можно увидеть на экране». ACM SIGGRAPH Computer Graphics . 19 (3): 181–189. doi :10.1145/325165.325235.
^ Мейер, Бертран (1988). Объектно-ориентированное построение программного обеспечения (1-е изд.). Prentice-Hall.
Внешние ссылки
На Викискладе есть медиафайлы по теме «Шаблон команды» .
В Wikibook Computer Science Design Patterns есть страница на тему: Реализации команд на разных языках.
Шаблон команды
Совет 68 по Java: узнайте, как реализовать шаблон «Команда» в Java