CodeLAB
на главную карта сайта обратная связь

Популярные задачи:

#Добавление истории операций(undo&redo) в компонент. (41035 hits)
#Сглаживание кривой В-сплайном. (39772 hits)
#Наибольший общий делитель. (195290 hits)
#Постепенное затемнение. (52178 hits)
#Заливка замкнутой области. (63607 hits)
#Вращение 3D объекта. (36956 hits)
#Как посчитать одинаковые пары за 1 проход (самая быстрая версия!). (3302 hits)
#Подключение. (28319 hits)
#ООП на javascript: классы, наследование, инкапсуляция. (259556 hits)
#Древовидные структуры. (58491 hits)
#Рисование полусферы. (30043 hits)
#Последовательный поиск и его оптимизации. (45597 hits)
#Поиск дубликатов внутри файла. (32337 hits)
#Замена символа строки. (445016 hits)
#Интерактивная, динамическая подгрузка картинок. (70895 hits)
#Динамическая очистка выпадающего списка (select) на javascript. (92725 hits)
#Вычисление минимального / максимального значения. (75664 hits)
#Обертки для массивов. (39767 hits)
#Постраничный вывод. (74085 hits)
#Косинус. (40835 hits)


Главная >> Каталог задач >> Паттерны >> Поведения >>

Команда (Command)

Aвтор:
Дата:
Просмотров: 142877
реализации(java: 5шт...) +добавить

Имя

«Паттерн
Command»

Команда- паттерн поведения объектов, инкапсулирующий различные алгоритмы в единую сущность. Встречается также под именами Action (действие), Transaction (транзакция).

Условия, Задача, Назначение

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

Мотивация

Иногда необходимо посылать объектам запросы, ничего не зная о том, выполнение какой операции запрошено и кто является получателем, в смысле кто будет ее исполнять. Например, в библиотеках для построения пользовательских интерфейсов встречаются такие объекты, как кнопки и меню, которые посылают запрос в ответ на действие пользователя.
Но в саму библиотеку не заложена возможность обрабатывать этот запрос, так как
только приложение, использующее ее, располагает информацией о том, что следует сделать. Проектировщик библиотеки не владеет никакой информацией о получателе запроса и о том, какие операции тот должен выполнить.
Паттерн команда позволяет библиотечным объектам отправлять запросы неизвестным объектам приложения, преобразовав (инкапсулировав) сам запрос в объект. Этот объект можно хранить и передавать, как и любой другой. В основе списываемого паттерна лежит абстрактный класс Command, в котором объявлен интерфейс для выполнения операций. В простейшей своей форме этот интерфейс состоит из одной абстрактной операции Execute. Конкретные подклассы Command определяют пару «исполнитель-действие», сохраняя исполнителя в переменной экземпляра, и реализуют операцию Execute, так чтобы она выполняла этот запрос. У получателя (исполнителя) есть информация, необходимая для выполнения запроса.

menu commands

С помощью объектов Command легко, например, реализуются меню. Каждый пункт меню - это экземпляр класса Menultem. Сами меню и все их пункты создает класс Application наряду со всеми остальными элементами пользовательского интерфейса. Класс Application отслеживает также открытые пользователем документы.
Приложение конфигурирует каждый объект MenuItem экземпляром конкретного подкласса Command. Когда пользователь выбирает некоторый пункт меню, ассоциированный с ним объект Menultem вызывает Execute для своего объекта-команды, a Execute выполняет операцию. Объекты Menultem не имеют информации, какой подкласс класса Command они используют (просто лишь вызывая метод выполнения Execute). Подклассы Command хранят информацию о получателе запроса и вызывают одну или несколько операций этого получателя.
Например, подкласс PasteCommand поддерживает вставку текста из буфера обмена в документ. Получателем для PasteCommand является Document, который был передан при создании объекта. Операция Execute вызывает операцию Paste документа-получателя.

А для подкласса OpenCommand операция Execute ведет себя по-другому: она запрашивает у пользователя имя документа, создает соответствующий объект Document, извещает о новом документе приложение-получатель и открывает этот документ.

Иногда объект Menultem должен выполнить последовательность команд.
Например, пункт меню для центрирования страницы стандартного размера можно было бы сконструировать сразу из двух объектов: CenterDocumentCommand и NormalsizeCommand. Поскольку такое комбинирование команд – явление обычное, то мы можем определить класс MacroCommand, позволяющий объекту Menultem выполнять произвольное число команд. MacroCommand - это конкретный подкласс класса Command, который просто выполняет последовательность команд. У него нет явного получателя, поскольку для каждой команды определен свой собственный.

Обратите внимание, что в каждом из приведенных примеров паттерн команда отделяет объект, инициирующий операцию, от объекта, который «знает», как ее выполнить (исполнитель или получатель). Это позволяет добиться высокой гибкости при проектировании пользовательского интерфейса. Пункт меню и кнопка одновременно могут быть ассоциированы в приложении с некоторой функцией, для этого достаточно приписать обоим элементам один и тот же экземпляр конкретного подкласса класса
Command. Мы можем динамически подменять команды, что очень полезно для реализации контекстно-зависимых меню. Можно также поддержать сценарии, если компоновать простые команды в более сложные. Все это выполнимо потому, что объект, инициирующий запрос, должен располагать информацией лишь о том, как его отправить, а не о том, как его выполнить.
 

Признаки применения, использования паттерна Команда (Command)

Используйте паттерн команда, когда хотите:
  1. Параметризовать объекты выполняемым действием.
    Как в случае с пунктами меню Menultem. В процедурном языке такую параметризацию можно выразить с помощью функции обратного вызова, то есть такой функции, которая регистрируется (динамически компонуется и создается таким образом в приложении), чтобы быть вызванной позднее. Команды представляют собой объектно-ориентированную альтернативу функциям обратного вызова.
  2. Определять, ставить в очередь и выполнять запросы в разное время.
    Время жизни объекта Command необязательно должно зависеть от времени жизни исходного запроса. Если получателя запроса удается реализовать так, чтобы он не зависел от адресного пространства, то объект-команду можно передать другому процессу, который займется его выполнением. Либо реализовать так что получатель не будет зависеть также от runtime-среды выполнения (виртуальной машины или сервера например) – то объект команду вполне можно сериализовать передать по какому-либо протоколу, далее десериализовать и выполнить уже на удаленном конце другим получателем.
  3. Поддержать отмену операций.
    Операция Execute объекта Command может сохранить состояние, необходимое для отката действий, выполненных командой. В этом случае в интерфейсе класса Command должна быть дополнительная операция Unexecute, которая отменяет действия, выполненные предшествующим обращением к Execute. Выполненные команды хранятся в списке истории. Для реализации произвольного числа уровней отмены и повтора команд нужно обходить этот список соответственно в обратном и прямом направлениях, вызывая при посещении каждого элемента команду Unexecute или Execute.
  4. Поддержать протоколирование изменений (лог).
    Чтобы их можно было выполнить повторно после аварийной остановки системы. Дополнив интерфейс класса Command операциями сохранения и загрузки, вы сможете вести протокол изменений во внешней памяти. Для восстановления после сбоя нужно будет загрузить сохраненные команды с диска и повторно выполнить их с помощью операции Execute.
  5. Структурировать систему на основе высокоуровневых операций, построенных из примитивных.
    Такая структура типична для информационных систем, поддерживающих транзакции. Транзакция инкапсулирует целый набор изменений данных. паттерн команда позволяет моделировать транзакции. У всех команд есть общий интерфейс, что дает возможность работать одинаково с любыми транзакциями. С помощью этого паттерна можно легко добавлять в систему новые виды транзакций.

Решение

Участники паттерна Команда (Command)

  1. Command – команда.
    Объявляет интерфейс для выполнения операций.
  2. ConcreteCommand (PasteCommand, OpenCommand) - конкретная команда.
    Определяет связь между объектом-получателем Receiver и действием.
    Реализует операцию Execute путем вызова соответствующих операций объекта Receiver;
  3. Client (Application) – клиент.
    Создает объекты класса ConcreteCommand и устанавливает их получателей.
  4. Invoker (Menultem) – инициатор.
    Обращается к команде для выполнения запроса.
  5. Receiver (Document, Application) – получатель.
    Располагает информацией о способах выполнения операций, необходимых для удовлетворения запроса. В роли получателя может выступать любой класс.

Схема использования паттерна Команда (Command)

Клиент создает объект ConcreteCommand и устанавливает для него получателя. Инициатор Invoker сохраняет объект ConcreteCommand. После когда запрашивается данная команда, инициатор отправляет запрос, вызывая операцию команды Execute. Если
поддерживается отмена выполненных действий, то ConcreteCommand перед вызовом Execute сохраняет информацию о состоянии, достаточную для выполнения отката. В конце концов, объект ConcreteCommand вызывает внутри себя операции получателя для выполнения запроса.

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

Вопросы, касающиеся реализации паттерна Команда (Command)

При реализации следует обратить внимание на следующие моменты:
  1. Насколько «умной» должна быть команда.
    У команды может быть широкий круг обязанностей. На одном полюсе стоит простое определение связи между получателем и действиями, которые нужно выполнить для удовлетворения запроса. На другом – независимая команда, т.е. реализация всего самостоятельно, без обращения за помощью к получателю. Последний вариант полезен, когда вы хотите определить команды, не зависящие от существующих классов, когда подходящего получателя не существует или когда получатель команде точно не известен. Например, команда, создающая новое окно приложения, может не понимать, что именно она создает, а трактовать окно, как любой другой объект. Где-то посередине между двумя крайностями находятся команды, обладающие достаточной информацией для динамического обнаружения своего получателя.
  2. Поддержка отмены и повтора операций.
    Команды могут поддерживать отмену и повтор операций, если имеется возможность отменить результаты выполнения (например, операцию Unexecute или Undo). В классе ConcreteCommand может сохраняться необходимая для этого дополнительная информация, в том числе:
    - объект-получатель Receiver, который выполняет операции в ответ на запрос;
    - аргументы операции, выполненной получателем;
    - исходные значения различных атрибутов получателя, которые могли измениться в результате обработки запроса.
    Получатель должен предоставить операции, позволяющие команде вернуться в исходное состояние. Для поддержки всего одного уровня отмены приложению достаточно сохранять только последнюю выполненную команду. Если же нужны многоуровневые отмена и повтор операций, то придется вести список истории выполненных команд. Максимальная длина этого списка и определяет число уровней отмены и повтора. Проход по списку в обратном направлении и откат результатов всех встретившихся по пути команд отменяет их действие.
    Проход в прямом направлении и выполнение встретившихся команд приводит к повтору выполнения действий.
    Команду, допускающую отмену, возможно, придется клонировать (побайтно) перед помещением в список истории. Дело в том, что объект команды, использованный для доставки запроса, скажем от пункта меню MenuItem, позже мог быть использован для других запросов. Поэтому копирование необходимо, чтобы определить разные вызовы одной и той же команды, если ее состояние при любом вызове может изменяться.
    Например, команда DeleteCoinmand, которая удаляет выбранные объекты, при каждом вызове должна сохранять разные наборы объектов. Поэтому объект DeleteCommand необходимо скопировать после выполнения, а копию поместить в список истории. Если в результате выполнения состояние команды никогда не изменяется, то копировать не нужно - в список достаточно поместить лишь ссылку на команду. Команды, которые обязательно нужно копировать перед помещением в список истории, ведут себя подобно прототипам.
  3. Накопление ошибок в процессе отмены.
    При обеспечении надежного, сохраняющего семантику механизма отмены и повтора может возникнуть проблема гистерезиса. При выполнении, отмене и повторе команд иногда накапливаются ошибки, в результате чего состояние приложения оказывается отличным от первоначального. Поэтому порой необходимо сохранять в команде больше информации, дабы гарантировать, что объекты будут целиком восстановлены. Чтобы предоставить команде доступ к этой информации, не раскрывая внутреннего устройства объектов, можно воспользоваться паттерном хранитель.
  4. Применение шаблонов в C++.
    Для команд, которые не допускают отмену и не имеют аргументов, в языке C++ можно воспользоваться шаблонами, чтобы не создавать подкласс класса Command для каждой пары действие-получатель.

Результаты

Результаты применения паттерна команда следующие:
  1. Команда разрывает связь между объектом, инициирующим операцию, и объектом, имеющим информацию о том, как ее выполнить.
  2. Команды - это самые настоящие объекты.
    Допускается манипулировать ими и расширять их точно так же, как в случае с любыми другими объектами, наследовать, компоновать, агрегировать для получения новых команд или модификации имеющихся.
  3. Из простых команд можно собирать составные.
    Например, класс MacroCommand, рассмотренный выше. В общем случае составные команды описываются паттерном компоновщик.
  4. Добавлять новые команды легко.
    Поскольку никакие существующие классы изменять не нужно.

Пример

В отличие от традиционных материалов, когда в качестве примера использования наперебой рассматривают реализацию через команды пунктов меню (главного и контекстного) – приведем другой не менее частый случай. В настоящее время довольно повсеместно используется идея модульного, взаимозаменяемого программирования, при котором у нас есть некий единый каркас приложения, а дополнительная функциональность реализуется в виде отдельных, независимых классов-модулей. При этом нет никаких взаимосвязей между каркасом и модулями, все полностью независимо: каркас лишь конфигурируется на этапе загрузки определенным количеством модулей далее по запросу пользователя, он вызывает на выполнение определенный модуль или сразу несколько, взаимодействуя с ними через единый «модульный» интерфейс, в котором обычно достаточно одной какой-либо операции запуска, типа Execute(), Run() и т.д.
Таким образом, модуль – это и есть команда, Command. Его подклассы реализуют всю логику каждый своего модуля.

Соответственно, наша система имеет единый общий каркас и наша задача добавить необходимые нам модули. Модульный интерфейс имеет вид: Module , где GetMenu() – возвращает содержимое меню управления данным модулем.

Допустим, нам понадобится модуль – галерея картинок: при выполнении должны будут отображаться список альбомов, либо список закаченных картинок. Реализация в общих чертах может быть такой: ImageGallery.
Обратим внимание, что данный модуль самостоятельно работает с базой данных, соединение с которой он сам создает. Типичный случай паттерна-команда: ни весь каркас приложения, ни другие модули – могут даже никак и не знать о существовании этой базы данных, в случае если с ней работает только модуль галерея. Только этот модуль сам решает, где ему хранить свои данные и откуда их забирать – из базы ли, из файлов и т.д., остальное окружение никак не осведомлено о том как внутри себя модуль реализует свою логику.
Далее нам нужен модуль отображения отчетов по продажам: SalesReports.
Реализация аналогична.
Теперь допустим у нас полно простых информационных страниц системы (сайта), на которых нужно просто отображать из базы информацию по идентификатору этой страницы. Чтобы каждый раз не создавать новый подкласс команды и потом не заморачиваться с поддержкой большого количества одинаковых сущностей – здесь уместней всего применить подход шаблонной команды: InfoPage.
Т.е. теперь проблема этого всего множества однотипных страниц решается очень просто, нужно лишь сконфигурировать каркас соответствующими экземплярами класс InfoPage, указывая каждый раз конкретные значения contentID и передавая общее соединение с базой (открытое ранее).
Ну и напоследок, помимо всего этого, у нас будут страницы, на которых нужно запускать несколько модулей вместе, например, вверху показывать содержимое одной из информационных страниц, а ниже – выводить все альбомы галереи картинок. Этого мы добиваемся с помощью т.н. макрокоманды (MacroCommand), манипулирующий целым списком команд: MultiPage.
Как видно, макрокоманда также реализует интерфейс общий для всех команд, а при своем запуске просто последовательно вызывает каждую из команд из своего списка.

 

Известные применения паттерна Команда (Command)

Быть может, впервые паттерн команда появился в работе Генри Либермана Henry Lieberman. В системе МасАрр команды широко применяются для реализации допускающих отмену операций. В ЕТ++, InterViews и Unidraw также имеются классы, описываемые паттерном команда. Так, в библиотеке Interviews определен абстрактный класс Action, который определяет всю функциональность команд. Есть и шаблон ActionCallback, параметризованный действием Action, который автоматически инстанцирует подклассы команд.

В библиотеке классов THINK также используются команды для поддержки отмены операций. В THINK команды называются задачами (Tasks). Объекты Task передаются по цепочке обязанностей, пока не будут кем-то обработаны.
Объекты команд в каркасе Unidraw уникальны в том отношении, что могут вести себя подобно сообщениям. В Unidraw команду можно послать другому объекту для интерпретации, результат которой зависит от объекта-получателя. Более того, сам получатель может делегировать интерпретацию следующему объекту, обычно своему родителю. Это напоминает паттерн цепочка обязанностей. Таким образом, в Unidraw получатель вычисляется, а не хранится. Механизм интерпретации в Unidraw использует информацию о типе, доступную во время выполнения.
Джеймс Коплиен описывает, как в языке C++ реализуются функторы - объекты, ведущие себя, как функции. За счет перегрузки оператора вызова operator() он становится более понятным. Смысл паттерна команда в другом - он устанавливает и поддерживает связь между получателем и функцией (то есть действием), а не просто функцию.

 

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

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

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

Реализации:

java(5)   +добавить

1) Module.java на java, code #487[автор:this]
2) ImageGallery.java на java, code #488[автор:this]
3) SalesReports.java на java, code #489[автор:this]
4) MultiPage.java на java, code #490[автор:this]
5) InfoPage.java на java, code #491[автор:this]