Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Design Patterns via C#.pdf
Скачиваний:
154
Добавлен:
17.03.2016
Размер:
13.25 Mб
Скачать

182

Мотивация

Рассмотрим пример с использованием паттерна Command в приложении, с пользовательским интерфейсом представленном в виде меню. Заметим, что библиотека пользовательских элементов .Net не содержит информации о способах обработки информации при выборе определенных пунктов меню. Варианты и алгоритмы обработки задаются разработчиками приложений, использующих библиотеки .Net. Кроме того, часто необходимо организовать обработку действий пользователя в условиях, когда изначально неизвестно, какой объект является получателем запроса на обработку, и неясно, выполнение какой конкретной задачи запрошено. Например, такое может случиться при разработке в больших командах, где разные группы разработчиков отвечают за логику работы приложения и за интерфейс или когда на этапе проектирования, проектировщик библиотеки интерфейсов не владеет информацией о том, какие классы будут задействованы в приложении, и какие операции они будут выполнять. В таком случае, нужно какимто образом инкапсулировать запрос и его параметры в определенном объекте и отделить объект интерфейса, инициирующий запрос, от объекта-исполнителя запроса.

Паттерн Command позволяет преобразовать запрос в объект и таким образом сделать возможной его хранение и отправку другим объектам приложения, не раскрывая при этом сути запроса и конкретного адресата. Основой паттерна является абстрактный класс Command, который задает интерфейс для выполнения операций, например, в виде абстрактного метода Execute, а также имеет защищенный метод логирования выполненных операций LogExecution:

abstract class Command

{

public abstract void Execute();

protected void LogExecution(string text)

{

MainForm.MainFormInstance.Log(this.GetType().Name + " -> " + text);

}

}

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

С помощью объектов унаследованных от абстрактного класса Command легко реализовать меню, адаптируя абстрактный класс Command к имеющемуся в библиотеке .Net классу ToolStripMenuItem:

class MyMenuItem : ToolStripMenuItem

{

public Command MenuCommand { get; set; }

183

public MyMenuItem(string text, Command command) : base(text)

{

MenuCommand = command;

}

protected override void OnClick(EventArgs e)

{

base.OnClick(e);

if (MenuCommand != null) MenuCommand.Execute();

}

}

Меню приложения будет состоять из объектов типа MyMenuItem, каждый из которых будет сконфигурирован экземпляром одного из конкретных подклассов класса Command: OpenCommand,

CopyCommand, CutCommand, SelectAllTextCommand, PasteCommand, CloseCommand, - или набором из таких объектов типа MacroCommand, которые содержат ссылку на получателя запроса. При выборе некоторого пункта меню связанный с ним объект класса MyMenuItem вызовет метод Execute, реализованный в классе инкапсулированного объекта-команды, который и выполнить необходимую операцию. Заметим, что объекты класса MyMenuItem, работают с интерфейсом заданным абстрактным классом Command и не содержат информацию о том, какую именно операцию они выполняют и каким именно объектом подкласса класса Command они оперируют. Классы конкретных команд содержат информацию о получателе запросов – объекте типа Document, доступному через статическое свойство CurrentDocument класса MainForm, и вызывают одну или целый набор операций этого получателя.

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

Применимость паттерна

Паттерн Command рекомендуется использовать, когда:

необходимо параметризировать объекты-команды выполняемым действием, например, кнопки или элементы меню, а также callback методы, благодаря объектно-ориентированному

представлению объектов-запросов. Заметим, что такая параметризация не является параметризацией типов в чистом виде, в языке С# - она представлена обобщениями, поддерживаемыми платформой .Net, хотя в этом случае обобщения (generics), также могут быть использованы, например, при параметризации сложными объектами классов Action<T> или Func<T, TResult >, где указатель на место заполнения типа T будет примать тип Command.

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

необходимо обеспечить отмену операций. Для этого нужно в интерфейсе абстрактного класса Command объявить метод Unxecute, вызов которого позволял бы осуществить откат действий, произведенных методом Execute. Возможно, для реализации такого действия, понадобиться предварительно сохранить необходимую информацию при выполнении метода Execute и

сохранять последовательность действий Execute/Unxecute в списке истории внутри объекта класса, чтобы при необходимости можно было выполнить откат состояния объекта-получателя, выполняя обратные операции. Чтобы предоставить команде доступ к этой информации, не раскрывая внутреннее состояние объектов-носителей информации, можно воспользоваться паттерном Memento.

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

184

класса Command методами сохранения и загрузки протокола изменений из внешнего источника. Тогда после сбоя можно будет загрузить протокол изменений и повторно выполнить последовательность операций при помощи методов Execute/Unxecute.

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

Результаты

Паттерн Command:

отделяет объект инициатор операции от объекта, имеющего информацию о том, как ее выполнить. Достигается благодаря адаптации (см. паттерн Adapter) абстрактного класса или интерфейса Command к классу объекта инициатора. При этом сам объект-инициатор может абсолютно не владеть информацией об объекте-исполнителе и о сути выполняемой операции.

позволяет оперировать объектами при маршрутизации и обработке запросов. Конкретные классы команд инкапсулируют в себе адрес обработчика операции и высокоуровневую часть алгоритма обработки.

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

например MacroCommand, из более простых составляющих. При этом объект сложной операции имеет тот же интерфейс, что и все его составные части, т.е. реализует метод Execute базового абстрактного класса Command (см. паттерн Composite).

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

при этом изменять не нужно.

Реализация

Полезные приемы реализации паттерна Command:

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

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

-адресе объекта-получателя класса Receiver, который выполняет операции по запросу;

185

-аргументы и другие параметры используемые при вызове методов класса объектаполучателя;

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

Также необходимым условием является предоставление доступа объектом Receiver к методам, позволяющим команде вернуть этот объект в исходное состояние. Обратим внимание, что если в результате выполнения команды меняется состояние не только объекта-получателя, но и самой команды, то объект команды также необходимо хранить в истории изменений, копируя его после выполнения, так как он может быть изменен при следующем вызове, и тогда выполнить откат действий корректно не удастся. Если в результате выполнения команды ее состояние не изменяется (не изменяются атрибуты объекта класса ConcreteCommand), то и хранить копию объекта не обязательно, достаточно хранения ссылки на объект-команду. Команды, которые изменяются при выполнении и нуждаются в хранении копий-клонов в списке истории ведут себя подобно прототипам (см. паттерн Prototype).

гарантирование целостности и неизменности объектов в процессе хранения истории изменений.

Чтобы гарантировать, что объекты будут полностью восстановлены в процессе отката или повтора команд и буду защищены от несанкционированного изменения можно воспользоваться паттерном Memento. Это позволит команде получить доступ объекта-команды к информации необходимой для произведения операций отмены/повтора, без изменения состояния хранимого объекта, и без раскрытия его внутренней структуры.

применение шаблонов. Для однотипных команд, которые не поддерживают операции отмены/повтора (Undo/Redo) можно воспользоваться подходом создания команды-шаблона – объекта типа SimpleCommand : Command на основе делегатов типов Action, Action<T>, Func<T, TResult>, или delegate, где метод Execute, будет вызывать необходимые callback методы. Такой подход позволяет сократить количество подклассов класса Command. Подробнее использование шаблонных команд рассматривается в примере ниже.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]