Лекция
Привет, мой друг, тебе интересно узнать все про паттерны поведения, тогда с вдохновением прочти до конца. Для того чтобы лучше понимать что такое паттерны поведения , настоятельно рекомендую прочитать все из категории Объектно-ориентированное программирование ООП.
паттерны поведения связаны с алгоритмами и распределением обязанностей между объектами. Речь в них идет не только о самих объектах и классах, но и о типичных способах взаимодействия. Паттерны поведения характеризуют сложный поток управления, который трудно проследить во время выполнения программы. Внимание акцентировано не на потоке управления как таковом, а на связях между объектами.
Название
Iterator (итератор). Cursor (курсор)
Задача
Предоставляет способ последовательного доступа ко всем элементам составного объекта, не раскрывая его внутреннего представления.
Мотивация
Составной объект, скажем список, должен предоставлять способ доступа к своим элементам, не раскрывая их внутреннюю структуру. Более того, иногда требуется обходить список по-разному, в зависимости от решаемой задачи. Но вряд ли вы захотите засорять интерфейс класса List операциями для различных вариантов обхода, даже если все их можно предвидеть заранее. Кроме того, иногда нужно, чтобы в один и тот же момент было определено несколько активных обходов списка. Все это позволяет сделать паттерн итератор. Основная его идея в том, чтобы за доступ к элементам и способ обхода отвечал не сам список, а отдельный объект - итератор. В классе Iterator определен интерфейс для доступа к элементам списка. Объект этого класса отслеживает текущий элемент, то есть он располагает информацией, какие элементы уже посещались. Например, класс List мог бы предусмотреть класс ListIterator.
Рисунок 2.21 Итератор
Прежде чем создавать экземпляр класса Listlterator, необходимо иметь список, подлежащий обходу. С объектом Listlterator вы можете последовательно посетить все элементы списка. Операция сurrentltem возвращает текущий элемент списка, операция first инициализирует текущий элемент первым элементом списка, Next делает текущим следующий элемент, a eof проверяет, не оказались ли мы за последним элементом, если да, то обход завершен.
Отделение механизма обхода от объекта List позволяет определять итераторы, реализующие различные стратегии обхода, не перечисляя их в интерфейсе класса List. Например, FilteringListIterator мог бы предоставлять доступ только к тем элементам, которые удовлетворяют условиям фильтрации. Заметим: между итератором и списком имеется тесная связь, клиент должен иметь информацию, что он обходит именно список, а не какую-то другую агрегированную структуру. Поэтому клиент привязан к конкретному способу агрегирования. Было бы лучше, если бы мы могли изменять класс агрегата, не трогая код клиента. Это можно сделать, обобщив концепцию итератора и рассмотрев полиморфную итерацию.Например, предположим, что у нас есть еще класс SkipList, реализующий список. Список с пропусками (skiplist) - это вероятностная структура данных, по характеристикам напоминающая сбалансированное дерево. Нам нужно научиться писать код, способный работать с объектами как класса List, так и класса SkipList.
Рисунок 2.22 Структура паттерна Iterator (пример)
Отделение механизма обхода от объекта List позволяет определять итераторы, реализующие различные стратегии обхода, не перечисляя их в интерфейсе класса List. Например, FilteringListIterator мог бы предоставлять доступ только к тем элементам, которые удовлетворяют условиям фильтрации. Заметим: между итератором и списком имеется тесная связь, клиент должен иметь информацию, что он обходит именно список, а не какую-то другую агрегированную структуру. Поэтому клиент привязан к конкретному способу агрегирования. Было бы лучше, если бы мы могли изменять класс агрегата, не трогая код клиента. Это можно сделать, обобщив концепцию итератора и рассмотрев полиморфную итерацию.Например, предположим, что у нас есть еще класс SkipList, реализующий список. Список с пропусками (skiplist) - это вероятностная структура данных, по характеристикам напоминающая сбалансированное дерево. Нам нужно научиться писать код, способный работать с объектами как класса List, так и класса SkipList.
Определим класс AbstractList, в котором объявлен общий интерфейс для манипулирования списками. Еще нам понадобится абстрактный класс Iterator, определяющий общий интерфейс итерации. Затем мы смогли бы определить конкретные подклассы класса Iterator для различных реализаций списка. В результате механизм итерации оказывается не зависящим от конкретных агрегированных классов.
Структура
Структура паттерна представлена на рис. 2.23.
Результаты
У паттерна итератор есть следующие важные особенности:
Рисунок 2.23 Структура паттерна Iterator
Название
Mediator (посредник)
Задача
Определяет объект, инкапсулирующий способ взаимодействия множества объектов. Посредник обеспечивает слабую связанность системы, избавляя объекты от необходимости явно ссылаться друг на друга и позволяя тем самым независимо изменять взаимодействия между ними.
Мотивация
Объектно-ориентированное проектирование способствует распределению некоторого поведения между объектами. Но при этом в получившейся структуре объектов может возникнуть много связей или (в худшем случае) каждому объекту придется иметь информацию обо всех остальных. Несмотря на то что разбиение системы на множество объектов в общем случае повышает степень повторного использования, однако изобилие взаимосвязей Приводит к обратному эффекту. Если взаимосвязей слишком много, тогда система подобна монолиту и маловероятно, что объект сможет работать без поддержки Других объектов. Более того, существенно изменить поведение системы практически невозможно, поскольку оно распределено между многими объектами. Если вы предпримете подобную попытку, то для настройки поведения системы вам придется определять множество подклассов.
Рассмотрим реализацию диалоговых окон в графическом интерфейсе пользователя. Здесь располагается ряд виджетов: кнопки, поля ввода и т.д., как Показано на рис. 2.24.
Рисунок 2.24. Форма для поиска
Часто между разными виджетами в диалоговом окне существуют зависимости. Например, если одно из полей ввода пустое, то определенная кнопка недоступна. При выборе из списка может измениться содержимое поля ввода. И наоборот, ввод текста в некоторое поле может автоматически привести к выбору одного или нескольких элементов списка. Если в поле ввода присутствует какой-то текст, то могут быть активизированы кнопки, позволяющие произвести определенное действие над этим текстом, например изменить либо удалить его.
Рисунок 2.25 Иерархия классов виджетов окна с использованием посредника
Рисунок 2.26 Типовая последовательность обработки события
В разных диалоговых окнах зависимости между виджетами могут быть различными. Поэтому, несмотря на то, что во всех окнах встречаются однотипные виджеты, просто взять и повторно использовать готовые классы виджетов не удастся, придется производить настройку с целью учета зависимостей. Индивидуальная настройка каждого виджета - утомительное занятие, ибо участвующих классов слитком много.
Всех этих проблем можно избежать, если инкапсулировать коллективное поведение в отдельном объекте посреднике. Посредник отвечает за координацию взаимодействий между группой объектов. Он избавляет входящие в группу объекты от необходимости явно ссылаться друг на друга. Все объекты располагают информацией только о посреднике, поэтому количество взаимосвязей сокращается. Так, классUIFind может служить посредником между виджетами в диалоговом окне. Объект этого класса «знает» обо всех виджетах в окне.
Структуру классов, полученную в результате применения посредника для примера, представленного на рис. 2.24, можно увидеть на рис. 2.25, а диаграмму взаимодействия – на рис. 2.26.
Структура
Рисунок 2.27 Структура паттерна Mediator
Результаты
У паттерна посредник есть следующие достоинства и недостатки:
Название
Observer (наблюдатель). Dependents (подчиненные), Publish-Subscribe (издатель-подписчик).
Задача
Определяет зависимость типа один ко многим между объектами таким образом, что при изменении состояния одного объекта все зависящие от него оповещаются об этом и автоматически обновляются.
Мотивация
В результате разбиения системы на множество совместно работающих классов появляется необходимость поддерживать согласованное состояние взаимосвязанных объектов. Но не хотелось бы, чтобы за согласованность надо было платить жесткой связанностью классов, так как это в некоторой степени уменьшает возможности повторного использования. Например, во многих библиотеках для построения графических интерфейсов пользователя презентационные аспекты интерфейса отделены от данных приложения. С классами, описывающими данные и их представление, можно работать автономно. Электронная таблица и диаграмма не имеют информации друг о друге, поэтому вы вправе использовать их по отдельности. Но ведут они себя так, как будто «знают» друг о друге. Когда пользователь работает с таблицей, все изменения немедленно отражаются на диаграмме, и наоборот.
При таком поведении подразумевается, что и электронная таблица, и диаграмма зависят от данных объекта и поэтому должны уведомляться о любых изменениях в его состоянии. И нет никаких причин, ограничивающих количество зависимых объектов; для работы с одними и теми же данными может существовать любое число пользовательских интерфейсов.
Паттерн наблюдатель описывает, как устанавливать такие отношения. Ключевыми объектами в нем являются субьект и наблюдатель. У субъекта может быть сколько угодно зависимых от него наблюдателей. Все наблюдатели уведомляются об изменениях в состоянии субъекта. Получив уведомление, наблюдатель опрашивает субъекта, чтобы синхронизировать с ним свое состояние. Такого рода взаимодействие часто называется отношением издатель-подписчик. Субъект издает или публикует уведомления и рассылает их, даже не имея информации о том, какие объекты являются подписчиками. На получение уведомлений может подписаться неограниченное количество наблюдателей.
Структура и участники
Рисунок 2.28 Структура паттерна Observer
Результаты
Паттерн наблюдатель позволяет изменять субъекты и наблюдатели независимо друг от друга. Субъекты разрешается повторно использовать без участия наблюдателей, и наоборот. Это дает возможность добавлять новых наблюдателей без модификации субъекта или других наблюдателей.
Рассмотрим некоторые достоинства и недостатки паттерна наблюдатель:
Название
State (состояние)
Задача
Позволяет объекту варьировать свое поведение в зависимости от внутреннего состояния. Извне создается впечатление, что изменился класс объекта.
Мотивация
Рассмотрим класс TCPConnection, с помощью которого представлено сетевое соединение. Объект этого класса может находиться в одном из нескольких состояний: Established (установлено), Listening (прослушивание), Closed (закрыто). Когда объект TCPConnection получает запросы от других объектов, То в зависимости от текущего состояния он отвечает по-разному. Например, ответ на запрос open (открыть) зависит от того, находится ли соединение в состоянии Closed или Established. Паттерн состояние описывает, каким образом объект TCPConnection может вести себя по-разному, находясь в различных состояниях.
Основная идея этого паттерна заключается в том, чтобы ввести абстрактный класс TCPState для представления различных состояний соединения. Этот класс объявляет интерфейс, общий для всех классов, описывающих различные рабочие состояния. В подклассах TCPState реализовано поведение, специфичное для конкретного состояния. Например, в классах TCPEstablished и TCPClosed реализовано поведение, характерное для состояний Established и Closed соответственно
Класс TCPConnection хранит у себя объект состояния (экземпляр некоторого подкласса TCPState), представляющий текущее состояние соединения, и делегирует все зависящие от состояния запросы этому объекту. TCPConnection использует свой экземпляр подкласса TCPState для выполнения операций, свойственных только данному состоянию соединения.
Рисунок 2.29 Структура паттерна State (пример)
При каждом изменении состояния соединения TCPConnection изменяет свой объект-состояние. Например, когда установленное соединение закрывается, TCPConnection заменяет экземпляр классаTCPEstablished экземпляром TCPClosed.
Паттерн State имеет смысл использовать в следующих ситуациях:
Структура
Рисунок 2.30 Структура паттерна State
Результаты
Локализует зависящее от состояния поведение и делит его на части, соответствующие состояниям. Паттерн состояние помещает все поведение, ассоциированное с конкретным состоянием, в отдельный объект. Поскольку зависящий от состояния код целиком находится в одном из подклассов класса State, то добавлять новые состояния и переходы можно просто путем порождения новых подклассов. Вместо этого можно было бы использовать данные-члены для определения внутренних состояний, тогда операции объекта Context проверяли бы эти данные. Об этом говорит сайт https://intellect.icu . Но в таком случае похожие условные операторы или операторы ветвления были бы разбросаны по всему коду класса Context. При этом добавление нового состояния потребовало бы изменения нескольких операций, что затруднило бы сопровождение. Паттерн состояние позволяет решить эту проблему, но одновременно порождает другую, поскольку поведение для различных состояний оказывается распределенным между несколькими подклассами State. Это увеличивает число классов. Конечно, один класс компактнее, но если состояний много, то такое распределение эффективнее, так как в противном случае пришлось бы иметь дело с громоздкими условными операторами. Наличие громоздких условных операторе в нежелательно, равно как и наличие длинных процедур. Они слишком монолитны, вот почему модификация и расширение кода становится проблемой. Паттерн состояние предлагает более удачный способ структурирования зависящего от состояния кода. Логика, описывающая переходы между состояниями, больше не заключена в монолитные операторы if илиswitch, а распределена между подклассами state. При инкапсуляции каждого перехода и действия в класс состояние становится полноценным объектом. Это улучшает структуру кода и проясняет его назначение;
Делает явными переходы между состояниями. Если объект определяет свое текущее состояние исключительно в терминах внутренних данных, то переходы между состояниями не имеют явного представления; они проявляются лишь как присваивания некоторым переменным. Ввод отдельных объектов для различных состояний делает переходы более явными. Кроме того, объекты State могут защитить контекст Context от рассогласования внутренних переменных, поскольку переходы с точки зрения контекста - это атомарные действия. Для осуществления перехода надо изменить значение только одной переменной (объектной переменной State в классе Context), а не нескольких;
Название
Strategy (стратегия). Policy (политика) .
Задача
Определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. Стратегия позволяет изменять алгоритмы независимо от клиентов, которые ими пользуются.
Мотивация
Существует много алгоритмов для разбиения текста на строки. Жестко "зашивать" вес подобные алгоритмы в классы, которые в них нуждаются, нежелательно по нескольким причинам:
Всех этих проблем можно избежать, если определить классы, инкапсулирующие различные алгоритмы разбиения на строки. Инкапсулированный таким образом алгоритм называется стратегией.
Рисунок 2.31 Структура паттерна Strategy (пример)
Предположим, что класс Composition отвечает за разбиение на строки текста, отображаемого в окне программы просмотра, и его своевременное обновление. Стратегии разбиения на строки определяются не в классе Composition, а в подклассах абстрактного класса Compositor. Это могут быть, например, такие стратегии:
Объект Composition хранит ссылку на объект Compositor. Всякий раз, когда объекту Composition требуется переформатировать текст, он делегирует данную обязанность своему объекту Compositor. Клиент указывает, какой объект Compositor следует использовать, параметризуя им объект Composition.
Используйте паттерн Стратегия, когда:
Структура и участники
Рисунок 2.32 Структура паттерна Strategy
Результаты
У паттерна стратегия есть следующие достоинства и недостатки:
void Composition::Repair ()
{
switch (_breakingStrategy) {
case SimpleStrategy:
ComposeWithSimpleCompositor();
break;
case TeXStrategy:
ComposeWithTeXCorapositor();
break;
// ...
}
// если необходимо, объединить результаты
// с имеющейся композицией
}
Паттерн стратегия позволяет обойтись без оператора переключения за счет делегирования задачи разбиения на строки объекту Strategy:
void Composition::Repair ()
{
_compositor->Compose();
// если необходимо, объединить результаты
// с имеющейся композицией
}
Если код содержит много условных операторов, то часто это признак того, что нужно применить паттерн стратегия;
Название
Template Method (шаблонный метод).
Задача
Шаблонный метод определяет основу алгоритма и позволяет подклассам переопределить некоторые шага алгоритма, не изменяя его структуру в целом.
Мотивация
Рассмотрим каркас приложения, в котором имеются классы Application Document. Класс Application отвечает за открытие существующих документов, хранящихся во внешнем формате, например в виде файла. Объект класса Document представляет информацию документа после его прочтения из файла.
Приложения, построенные на базе этого каркаса, могут порождать подклассы от классов Application и Document, отвечающие конкретным потребностям. Например, графический редактор определит подклассы DrawApplication и DrawDocument, а электронная таблица - подклассы SpreadsheetApplication и SpreadsheetDocument.
Рисунок 2.33 Структура паттерна Template Method (пример)
В абстрактном классе Application определен алгоритм открытия и считывания документа в операции OpenDocument;
void Application::openDocument (const char* name)
{
if (!CanOpenDocument(name)) {
// работа с этим документом
// невозможна return;
}
Document* doc = DoCreateDocument();
if (doc) {
_docs->AddDocument(doc);
AboutToOpen Document (doc);
doc->0pen();
doc->DoRead();
}
}
Операция openDocument определяет все шаги открытия документа. Она проверяет, можно ли открыть документ, создает объект класса Document, добавляет его к набору документов и считывает документ из файла.
Операцию вида openDocument мы будем называть шаблонным методом, описывающим алгоритм в терминах абстрактных операций, которые замещены в подклассах для получения нужного поведения. Подклассы класса Application выполняют проверку возможности открытия (canOpenDocument) и создания документ (doCreateDocument). Подклассы класса Document считывают документ (doRead). Шаблонный метод определяет также операцию, которая позволяет подклассам Application получить информацию о том, что документ вот-вот будет открыт (aboutToOpenDocurnent). Определяя некоторые шаги алгоритма с помощью абстрактных операций, шаблонный метод фиксирует их последовательность, но и позволяет реализовать их в подклассах классов Application и Document.
Структура
Рисунок 2.34 Структура паттерна Template Method
Результаты
Шаблонные методы - один из фундаментальных приемов повторного использования кода. Они особенно важны в библиотеках классов, поскольку предоставляют возможность вынести общее поведение в библиотечные классы.
Шаблонные методы приводят к инвертированной структуре кода, которую иногда называют принципом Голливуда, подразумевая часто употребляемую в этой кино-империи фразу «Не звоните нам, мы сами позвоним». В данном случае это означает, что родительский класс вызывает операции подкласса, а не наоборот.
Шаблонные методы вызывают операции следующих видов:
продолжение следует...
Часть 1 2.4 Паттерны поведения
Часть 2 2.4.7 Паттерн Chain Of Responsibility - 2.4 Паттерны поведения
Часть 3 - 2.4 Паттерны поведения
Комментарии
Оставить комментарий
Объектно-ориентированное программирование ООП
Термины: Объектно-ориентированное программирование ООП