Паттерны проектирования. Repository & Decorator

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

Рубрика Программирование, компьютеры и кибернетика
Вид реферат
Язык русский
Дата добавления 21.09.2013
Размер файла 686,9 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

Размещено на http://www.allbest.ru/

Министерство образования и науки Украины

Днепропетровский национальный университет имени Олеся Гончара

Факультет прикладной математики

Кафедра математического обеспечения ЭВМ

Реферат

По предмету: «Объектно-ориентированное программирование»

На тему: «Паттерны проектирования. Repository & Decorator»

Выполнила: Ярослава Юрьевна Чернопищенко

Днепропетровск

Паттерны проектирования (Design Patterns)

Паттернами проектирования (Design Patterns) называют решения часто встречающихся проблем в области разработки программного обеспечения. Паттерны проектирования не являются готовыми решениями, которые можно трансформировать непосредственно в код, а представляют общее описание решения проблемы, которое можно использовать в различных ситуациях.

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

Существуют несколько типов паттернов проектирования, каждый из которых предназначен для решения своего круга задач:

· Порождающие паттерны, предназначенные для создания новых объектов в системе.

· Структурные паттерны, решающие задачи компоновки системы на основе классов и объектов.

· Паттерны поведения, предназначенные для распределения обязанностей между объектами в системе.

Порождающие паттерны

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

Пусть мы разрабатываем многообещающую стратегическую игру под названием "Пунические войны", описывающую великое военное противостояние между Римской Республикой и Карфагеном (264 -- 146 г. до н. э.). Персонажами игры могут быть воины трех типов: пехота, конница и лучники. Каждый из этих видов обладает своими отличительными характеристиками, такими как внешний вид, боевая мощь, скорость передвижения и степень защиты. Несмотря на такие отличия, у всех видов боевых единиц есть общие черты. Например, все они могут передвигаться по игровому полю в различных направлениях, хотя всадники делают это быстрее всех. Или каждая боевая единица имеет свой уровень здоровья, и если он становится равным нулю, воин погибает. При этом уничтожить лучника значительно проще, чем другие виды воинов.

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

class Warrior

{

public:

virtual void info() = 0;

virtual ~Warrior() {}

};

class Infantryman: public Warrior

{

public:

void info() { cout << "Infantryman" << endl; }

};

class Archer: public Warrior

{

public:

void info() { cout << "Archer" << endl; }

};

class Horseman: public Warrior

{

public:

void info() { cout << "Horseman" << endl; }

};

Полиморфный базовый класс Warrior определяет общий интерфейс, а производные от него классы Infantryman, Archer и Horseman реализуют особенности каждого вида воина.

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

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

enum Warrior_ID { Infantryman_ID=0, Archer_ID, Horseman_ID };

Warrior * сreateWarrior( Warrior_ID id )

{

Warrior * p;

switch (id)

{

case Infantryman_ID:

p = new Infantryman();

break;

case Archer_ID:

p = new Archer();

break;

case Horseman_ID:

p = new Horseman();

break;

default:

assert( false);

}

return p;

}

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

Познакомившись с основными проблемами, возникающими при создании объектов новых типов, кратко рассмотрим особенности каждого из порождающих паттернов (шаблонов).

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

Паттерн Abstract Factory использует несколько фабричных методов и предназначен для создания целого семейства или группы взаимосвязанных объектов. Для случая нашей стратегической игры такими взаимосвязанными объектами могли бы быть объекты всех персонажей (воины и крестьяне) для конкретной расы. Паттерн Builder определяет процесс поэтапного конструирования сложного объекта, в результате которого могут получаться разные представления этого объекта.

Паттерн Prototype создает новые объекты с помощью прототипов. Прототип - некоторый объект, умеющий создавать по запросу копию самого себя. Паттерн Singleton контролирует создание единственного экземпляра некоторого класса и предоставляет доступ к нему.

Паттерн Object Pool используется в случае, когда создание объекта требует больших затрат или может быть создано только ограниченное количество объектов некоторого класса. Новичкам можно рекомендовать изучение порождающих паттернов в следующей последовательности: Singleton, Factory Method, Abstract Factory, Prototype, остальные паттерны.

Структурные паттерны

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

· Наследование, когда базовый класс определяет интерфейс, а подклассы - реализацию. Структуры на основе наследования получаются статичными.

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

Кратко рассмотрим особенности структурных паттернов (шаблонов).

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

Паттерн Bridge отделяет абстракцию от реализации так, что то и другое можно изменять независимо.

Паттерн Composite группирует схожие объекты в древовидные структуры. Рассматривает единообразно простые и сложные объекты.

Паттерн Decorator используется для расширения функциональности объектов. Являясь гибкой альтернативой порождению классов, паттерн Decorator динамически добавляет объекту новые обязанности.

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

Паттерн Flyweight использует разделение для эффективной поддержки множества объектов.

Паттерн Proxy замещает другой объект для контроля доступа к нему.

Паттерны поведения

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

Кратко рассмотрим особенности паттернов (шаблонов) поведения.

Паттерн Chain of Responsibility позволяет обработать запрос нескольким объектам-получателям. Получатели связываются в цепочку, и запрос передается по цепочке, пока не будет обработан каким-то объектом. Паттерн Chain of Responsibility позволяет также избежать жесткой зависимости между отправителем запроса и его получателями.

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

Паттерн Iterator предоставляет механизм обхода элементов составных объектов (коллекций) не раскрывая их внутреннего представления.

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

Паттерн Mediator инкапсулирует взаимодействие совокупности объектов в отдельный объект-посредник. Уменьшает степень связанности взаимодействующих объектов - им не нужно хранить ссылки друг на друга.

Паттерн Memento получает и сохраняет за пределами объекта его внутреннее состояние так, чтобы позже можно было восстановить объект в таком же состоянии. Паттерн Observer определяет зависимость "один-ко-многим" между объектами так, что при изменении состояния одного объекта все зависящие от него объекты уведомляются и обновляются автоматически.

Паттерн State позволяет объекту изменять свое поведение в зависимости от внутреннего состояния. Создается впечатление, что объект изменил свой класс. Паттерн State является объектно-ориентированной реализацией конечного автомата.

Если поведение системы настраивается согласно одному из некоторого множества алгоритму, то применение паттерна Strategy переносит семейство алгоритмов в отдельную иерархию классов, что позволяет заменять один алгоритм другим в ходе выполнения программы. Кроме того, такую систему проще расширять и поддерживать.

Паттерн Template Method определяет основу алгоритма и позволяет подклассам изменить некоторые шаги этого алгоритма без изменения его общей структуры.

Паттерн Visitor определяет операцию, выполняемую на каждом элементе из некоторой структуры без изменения классов этих объектов.

Хранилище (Repository)

Выступает в роли посредника между слоем домена и слоем отображения данных, предоставляя интерфейс в виде коллекции для доступа к объектам домена.

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

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

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

Принцип действия

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

Несмотря на сложные механизмы, реализованные внутри хранилища, его интерфейс очень прост. Клиент создает объект критериев, указывая характеристики объектов, которые должны быть возвращены в результате выполнения запроса. Например, чтобы найти сотрудников с указанным именем, необходимо задать объект критериев, описывая каждый критерий примерно следующим образом: criteria, equals (Person. LAST_NAME, "Fowler") и criteria, like (Person. FIRST_NAME, "M"). Затем нужно вызвать метод repository.matching (criteria), чтобы возвратить список сотрудников с фамилией Fowler, имя которых начинается на букву "М". Для большего удобства в абстрактном классе хранилища можно определить и другие методы; например, для поиска единственного соответствия было бы неплохо воспользоваться методом наподобие soleMatch (criteria) , который бы возвращал объект, а не коллекцию объектов. В число других распространенных методов входит метод поиска по идентификатору byObject id (id), который совсем несложно реализовать, используя метод soleMatch.

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

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

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

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

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

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

Назначение

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

Как правило, это необходимо для того, чтобы проводить тестирование исключительно на объектах приложения. Большинство тестов выполняются гораздо быстрее, если в них не осуществляется доступ к базе данных. Создание средств для тестирования модулей приложения может оказаться еще проще, если от вас потребуется всего лишь построить несколько объектов домена и поместить их в коллекцию, вместо того чтобы сохранять в базе данных в начале работы и удалять оттуда по ее окончании.

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

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

Дополнительные источники информации

Пример: поиск подчиненных заданного сотрудника (Java)

С точки зрения клиента, использование хранилища не представляет собой ничего сложного. Чтобы извлечь из базы данных список подчиненных соответствующего сотрудника, объект Person создает объект Criteria, содержащий описание критерия поиска, и отсылает его нужному хранилищу.

public class Person {

public List dependents() {

Repository repository = Registry.personRepository();

Criteria criteria = new Criteria();

criteria.equal(Person.BENEFACTOR, this);

return repository.matching(criteria); }

}

Для выполнения наиболее распространенных запросов можно реализовать специализированные производные классы хранилищ. В нашем примере можно было создать класс PersonRepository, производный от Repository, и переместить создание критерия поиска в новый класс.

public class PersonRepository extends Repository {

public List dependentsOf(Person aPerson) {

Criteria criteria = new Criteria();

criteria.equal(Person.BENEFACTOR, aPerson);

return matching(criteria); }

}

После этого объект Person сможет вызвать метод dependentsOf () прямо из своегохранилища PersonRepository.

public class Person {

public List dependents() {

return Registry.personRepository().dependentsOf(this);

}

}

Пример: выбор стратегий хранилища (Java)

Поскольку интерфейс хранилища надежно скрывает источник данных от слоя домена, можно изменить реализацию логики запросов внутри хранилища без изменения вызовов, поступающих от клиентов. Действительно, код домена не должен "беспокоиться" об источнике или назначении объектов домена. Когда источник данных расположен в оперативной памяти, то для обработки этого случая необходимо изменить метод matching () так, чтобы он просматривал коллекцию объектов домена на предмет соответствия заданному критерию. При этом не предполагается изменять источник данных "навсегда"; скорее, нас интересует возможность быстро переключаться между источниками данных, когда это потребуется. Отсюда следует необходимость изменить метод matching () так, чтобы он делегировал выполнение запроса тому или иному объекту стратегии. Очевидное преимущество такого подхода -- возможность определить несколько стратегий, настроив каждую из них для обработки конкретной ситуации. В нашем случае целесообразно создать два объекта стратегии: объект Relationalstrategy, который будет выполнять запрос к базе данных, и объект inMemoryStrategy, который будет просматривать коллекцию объектов домена в оперативной памяти. Каждый объект стратегии реализует интерфейс Repositorystrategy, который предоставляет доступ к методу matching (), поэтому реализация класса Repository будет выглядеть, как показано ниже.

abstract class Repository {

private Repositorystrategy strategy;

protected List matching(Criteria aCriteria) {

return strategy.matching(aCriteria); }}

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

public class Relationalstrategy implements Repositorystrategy {

protected List matching(Criteria criteria) {

Query query = new Query(myDomainObjectClass())

query.addCriteria(criteria);

return query.execute(unitOfWork()); }}

Паттерн Decorator

Декоратор - паттерн, структурирующий объекты, расширяя их функциональность. Также фигурирует под именем Wrapper.

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

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

Мотивация

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

Добавить новые обязанности допустимо с помощью наследования. При наследовании классу с рамкой вокруг каждого экземпляра подкласса будет рисоваться рамка. Однако это решение статическое, которое нельзя реализовать динамически в процессе выполнения программы, а значит, недостаточно гибкое. Клиент не может управлять оформлением компонента рамкой.

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

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

Предположим, что имеется объект класса TextView, который отображает текст в окне. По умолчанию TextView не имеет полос прокрутки, поскольку они не всегда нужны. Но при необходимости их удастся добавить с помощью декоратора ScrollDecorator. Допустим, что еще мы хотим добавить жирную сплошную рамку вокруг объекта TextView. Здесь может помочь декоратор BorderDecorator.

Мы просто передаем сначала объект TextView в декоратор ScrollDecorator, и затем полученный результирующий объект передаем далее в BorderDecorator - получаем искомый результат.

Ниже на диаграмме показано, как композиция объекта TextView с объектами BorderDecorator и ScrollDecorator порождает элемент для ввода текста, окруженный рамкой и снабженный полосой прокрутки.

Классы ScrollDecorator и BorderDecorator являются подклассами Decorator - абстрактного класса, который представляет визуальные компоненты, применяемые для оформления других визуальных компонентов.

VisualComponent - это абстрактный класс для представления визуальных объектов. В нем определен интерфейс для рисования и обработки событий. Отметим, что класс Decorator просто переадресует запросы на рисование своему компоненту, а его подклассы могут расширять эту операцию.

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

Важная особенность этого паттерна состоит в том, что декораторы могут употребляться везде, где возможно появление самого объекта VisualComponent. Клиенту неважно с каким объектом работать: декорированным или недекорированным, он никоим образом не зависит от наличия или отсутствия оформлений.

паттерн поведение декоратор репозиторий

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

Используйте паттерн декоратор когда:

1. Для динамического и прозрачного для клиентов добавления новых возможностей, «фич» объектам;

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

3. Когда расширение путем порождения подклассов по каким-то причинам неудобно или невозможно. Иногда приходится реализовывать много независимых расширений, так что порождение подклассов для поддержки всех возможных комбинаций приведет к комбинаторному росту их числа («комбинаторный взрыв»). В других случаях определение класса может быть скрыто или почему-либо еще недоступно, так что породить от него подкласс нельзя.

Участники паттерна Декоратор (Decorator)

1. Component (VisualComponent) - компонент. Класс, задает интерфейс для объектов, на которые могут быть динамически возложены дополнительные обязанности, ровно как задает его и для будущих декораторов.

2. ConcreteComponent (TextView) - конкретный компонент. Определяет объект, на который возлагаются дополнительные обязанности, вешаются дополнительные возможности.

3. Decorator - декоратор. Хранит ссылку на объект Component и наследует реализацию его интерфейса по-умалчанию;

4. ConcreteDecorator (BorderDecorator, ScrollDecorator) - конкретный декоратор. Возлагает дополнительные обязанности на компонент.

Схема использования паттерна Декоратор (Decorator)

Клиенты работают только с интерфейсом Component. Когда нужно добавить функциональность в объект типа ConcreteComponent - берут соответствующий ConcreteDecorator, реализующий эту функциональность, конфигурируют, передают ему ссылку на первоначальный объект ConcreteComponent и прозрачно подсовывают клиенту. Клиент, вызывая те же методы, начинает вызывать их у ConcreteDecorator-а, тот, выполнив свою миссию, далее делегирует их первоначальному объекту ConcreteComponent.

Вопросы, касающиеся реализации паттерна Декоратор (Decorator)

Применение паттерна декоратор требует рассмотрения следующих вопросов:

1. Соответствие интерфейсов. Интерфейс декоратора должен соответствовать интерфейсу декорируемого компонента. Поэтому классы ConcreteDecorator должны наследоваться от общего класса;

2. Отсутствие абстрактного класса Decorator. Нет необходимости определять класс Decorator абстрактным, если планируется добавить всего одну обязанность. Так часто происходит, когда вы работаете с уже существующей иерархией классов, а не проектируете новую. В таком случае ответственность за переадресацию запросов, которую обычно несет класс Decorator, можно возложить непосредственно на ConcreteDecorator, что конечно не помешает потом добавить новые декораторы, если потребуется: просто будет уже присутствовать некоторая реализация по умолчанию.

3. Облегченные классы Component. Чтобы можно было гарантировать соответствие интерфейсов, компоненты и декораторы должны наследовать общему классу Component. Важно, чтобы этот класс был настолько легким, насколько возможно. Иными словами, он должен определять интерфейс, а не хранить данные. В противном случае декораторы могут стать весьма тяжеловесными, и применять их в большом количестве будет накладно. Включение большого числа функций в класс Component также увеличивает вероятность, что конкретным подклассам придется платить за то, что им не нужно;

4. Изменение представления, а не внутреннего устройства объекта. Декоратор можно рассматривать как появившуюся у объекта оболочку, которая изменяет его поведение. Противовес - изменение внутреннего устройства объекта, хорошим примером чего может служить паттерн стратегия. Стратегии лучше подходят в ситуациях, когда класс Component уже достаточно тяжел, так что применение паттерна декоратор обходится слишком дорого. В паттерне стратегия компоненты передают часть своей функциональности отдельному объекту-стратегии, поэтому изменить или расширить поведение компонента можно, просто заменив этот объект. Например, мы можем поддержать разные стили рамок, поручив рисование рамки специальному объекту Border. Объект Border является примером объекта-стратегии: в данном случае он инкапсулирует стратегию рисования рамки. Число стратегий может быть любым, поэтому эффект такой же, как от рекурсивной вложенности декораторов.

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

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

При использовании подхода, основанного на стратегиях, может возникнуть необходимость модифицировать компонент, чтобы он соответствовал новому расширению. В общем случае у стратегии может быть свой собственный специализированный интерфейс, тогда как интерфейс декоратора должен повторять интерфейс компонента. Например, стратегии рисования рамки необходимо определить всего лишь интерфейс для этой операции (DrawBorder, GetWidth и т.д.), то есть класс стратегии может быть легким, несмотря на тяжеловесность компонента.

Системы МасАрр и Bedrock применяют такой подход не только для оформления видов, но и для расширения особенностей поведения объектов, связанных с обработкой событий. В обеих системах вид ведет список объектов поведения, которые могут модифицировать и перехватывать события. Каждому зарегистрированному объекту поведения вид предоставляет возможность обработать событие до того, как оно будет передано незарегистрированным объектам такого рода, за счет чего достигается переопределение поведения. Можно, например, декорировать вид специальной поддержкой работы с клавиатурой, если зарегистрировать объект поведения, который перехватывает и обрабатывает события нажатия клавиш.

Результаты

У паттерна декоратор есть, по крайней мере, 2 плюса и два минуса, плюсы:

1. Большая гибкость, нежели у статического наследования. Паттерн декоратор позволяет более гибко добавлять объекту новые обязанности, чем это было бы возможно в случае статического (множественного) наследования. Декоратор может добавлять и удалять эту функциональность во время выполнения программы. При использовании же наследования требуется создавать новый класс для каждой дополнительной обязанности (например, Bor.deredScrollableTextView, BorderedTextView), что ведет к увеличению числа классов и, как следствие, к возрастанию сложности системы. Кроме того, применение нескольких декораторов к одному компоненту позволяет произвольным образом сочетать обязанности. Декораторы позволяют легко добавить одно и то же свойство дважды. Например, чтобы окружить объект TextView двойной рамкой, нужно просто добавить два декоратора BorderDecorators. Двойное наследование классу Border в лучшем случае чревато ошибками;

2. Позволяет избежать перегруженных функциями классов на верхних уровнях иерархии. Декоратор разрешает добавлять новые обязанности по мере необходимости. Вместо того чтобы пытаться поддержать все мыслимые возможности в одном сложном, допускающем разностороннюю настройку классе, вы можете определить простой класс и постепенно наращивать его функциональность с помощью декораторов. В результате приложение уже не платит за неиспользуемые функции. Нетрудно также определять новые виды декораторов независимо от классов, которые они расширяют, даже если первоначально такие расширения не планировались. При расширении же сложного класса обычно приходится вникать в детали, не имеющие отношения к добавляемой функции;

и минусы:

Декоратор и его компонент не идентичны. Декоратор действует как прозрачное обрамление. Но декорированный компонент все же не идентичен исходному. При использовании декораторов это следует иметь в виду;

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

Применение паттерна декоратор довольно распространено. Один из ближайших примеров - фреймворк автоматизированного тестирования JUnit.

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

Конфигурирующийся соответственно объектом типа Test (JUnit4TestAdapter например).

Фрейворк добавляет такие конкретные виды декораторов, как: junit.extensions.RepeatedTest.java - позволяющий многократное выполнение теста, а также другой декоратор: junit.extensions.TestSetup.java- добавляющий возможность выполнять дополнительные действия до и после выполнения теста.

Соответственно, чтобы добавить эту функциональность, можно, например, создать объект типа TestSetup, передав ему имеющийся объект типа Test, далее чтобы добавить еще и многократное выполнение, создаем объект типа RepeatedTest, передавая туда созданный ранее объект типа TestSetup.

Известные применения паттерна Декоратор (Decorator)

Во многих библиотеках для построения объектно-ориентированных интерфейсов пользователя декораторы применяются для добавления к виджетам графических оформлений. В качестве примеров можно назвать Interviews, ЕТ++ и библиотеку классов Smalltalk. Другие варианты применения паттерна декоратор - это класс DebuggingGlyph из библиотеки Interviews и PassivityWrapper из ParcPlace Smalltalk. DebuggingGlyph печатает отладочную информацию до и после того, как переадресует запрос на размещение своему компоненту. Эта информация может быть полезна для анализа и отладки стратегии размещения объектов в сложном контейнере. Класс PassivityWrapper позволяет разрешить или запретить взаимодействие компонента с пользователем.

Но применение паттерна декоратор никоим образом не ограничивается графическими интерфейсами пользователя, как показывает следующий пример, основанный на потоковых классах из каркаса ЕТ++.

Поток - это фундаментальная абстракция в большинстве средств ввода/вывода. Он может предоставлять интерфейс для преобразования объектов в последовательность байтов или символов. Это позволяет записать объект в файл или буфер в памяти и впоследствии извлечь его оттуда. Самый очевидный способ сделать это - определить абстрактный класс Stream с подклассами MemoryStream и FileStream. Предположим, однако, что нам хотелось бы еще уметь:

· компрессировать данные в потоке, применяя различные алгоритмы сжатия (кодирование повторяющихся серий, алгоритм Лемпеля-Зива и т.д.)

· преобразовывать данные в 7-битные символы кода ASCII для передачи по каналу связи

Абстрактный класс Stream имеет внутренний буфер и предоставляет операции для помещения данных в поток (Putlnt, PutString). Как только буфер заполняется, Stream вызывает абстрактную операцию HandleBufferFull, которая выполняет реальное перемещение данных. В классе FileStream эта операция замещается так, что буфер записывается в файл. Ключевым здесь является класс StreamDecorator. Именно в нем хранится ссылка на тот поток-компонент, которому переадресуются все запросы.

Паттерн декоратор позволяет весьма элегантно добавить такие обязанности потокам. На диаграмме ниже показано одно из возможных решений задачи:

Подклассы StreamDecorator замещают операцию HandleBufferFull и выполняют дополнительные действия, перед тем как вызвать реализацию этой операции в классе StreamDecorator.

Например, подкласс CompressingStream сжимает данные, a ASCII7Stream конвертирует их в 7-битный код ASCII. Теперь, для того чтобы создать объект FileStream, который одновременно сжимает данные и преобразует результат в 7-битный код, достаточно просто декорировать FileStream с использованием CompressingStream и ASCII7Stream:

Stream* aStream = new CompressingStream (

new ASCII7Stream(

new FileStream ( "aFileName")

aStream->PutInt(12);

aStream->PutString("aString");

Список литературы

1. Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приемы объектно-ориентированного проектирования. Паттерны проектирования. -- СПб: Питер, 2001. -- 368 с.: ил.

2. Фаулер, Мартин. Архитектура корпоративных программных приложений.: Пер. с англ. -- М.: Издательский дом "Вильямс", 2006. -- 544 с.: ил. -- Парал. тит. англ.

3. http://design-pattern.ru/ - Справочник по паттернам проектирования

Размещено на Allbest.ru


Подобные документы

  • Основные типы шаблонов проектирования. Типы связей, которые могут применяться при объектно-ориентированном программировании. Обзор и реализация порождающих, структурных и поведенческих шаблонов проектирования. Шаблоны "Command", "Front Controller".

    курсовая работа [3,1 M], добавлен 01.06.2014

  • Особенности исследования методик объектно-ориентированного проектирования программ с помощью языка UML по формализации, решению поставленной задачи, технологических приемов разработки объектно-ориентированных программ на языке Си++. Разработка программы.

    контрольная работа [188,9 K], добавлен 22.10.2014

  • Особенности объектно-ориентированного проектирования. Основные понятия объектно-ориентированного подхода. Основы языка UML, варианты его использования. Диаграммы классов и взаимодействия. Разработка диаграммы прецедентов (вариантов использования).

    курсовая работа [1,1 M], добавлен 13.05.2014

  • Описание проектного решения стратегической системы, этапы объектно-ориентированного анализа и проектирования. Описание связей между объектами. Программная реализация, построение модели состояний объекта. Руководство пользователя и описание программы.

    курсовая работа [388,8 K], добавлен 17.11.2011

  • Изучение базовых понятий объектно-ориентированного программирования. Исследование принципов работы с классами и объектами. Построение системы классов для описания плоских геометрических фигур. Анализ методов создания объектов, перемещения на плоскости.

    лабораторная работа [212,0 K], добавлен 10.03.2013

  • Унифицированный язык моделирования. Методы объектно-ориентированного анализа и проектирования. Создание диаграммы последовательности и диаграммы сотрудничества. Главная диаграмма классов. Добавление связей между классами. Зависимость между пакетами.

    курсовая работа [2,7 M], добавлен 23.06.2011

  • Общая характеристика объектно-ориентированного подхода в программировании, его основные свойства и принципы. Разработка программы для автоматизация деятельности кафе на основе объектно-ориентированного подхода, проектирования и реализации схемы данных.

    курсовая работа [1,2 M], добавлен 22.01.2012

  • Использование объектно-ориентированного программирования - хорошее решение при разработке крупных программных проектов. Объект и класс как основа объектно-ориентированного языка. Понятие объектно-ориентированных языков. Языки и программное окружение.

    контрольная работа [60,1 K], добавлен 17.01.2011

  • Обзор процесса проектирования. Характерные черты удачных проектов. Понятие и типы домена. Способ обработки событий. Архитектурные классы Form, Imitator, AE. Статическая модель прикладного домена. Исходные тексты операций обработки событий и их описание.

    курсовая работа [375,4 K], добавлен 26.01.2011

  • Понятие о классах и объектах, их место и значение в современном программировании. История развития унифицированного языка моделирования, достижения в данной области на сегодня. Методы представления класса. Представление отношения между классами.

    реферат [515,6 K], добавлен 25.01.2011

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.