Аналитические шаблоны проектирования приложений

Поведенческие шаблоны проектирования

Разбить на страницы
Показывать лекцию целиком

Введение

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

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

Шаблоны проектирования поведения объектов

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

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

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

Интерпретатор

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

В этом случае целесообразно разработать шаблон "Интерпретатор", который решает данную задачу.

"Интерпретатор" (англ. Interpreter) – поведенческий шаблон проектирования, решающий часто встречающуюся, но подверженную изменениям задачу. Также известен как Little (Small) Language.

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

  • Абстрактное выражение.
  • Определяет интерфейс выражения, объявляет метод шаблона.
  • Терминальное выражение.
  • Реализует методы. Для каждого объекта создается свое терминальное выражение.
  • Нетерминальное выражение.
  • Представляет правило. Для каждого отдельного правила создается свой объект нетерминального выражения.
  • Контекст.
  • Содержит общую информацию. Может использоваться объектами терминальных и нетерминальных выражений для сохранения состояния операций и последующего доступа к сохраненному состоянию.
  • Клиент.
  • Управляет выполнением бизнес-логики в виде абстрактного синтаксического дерева, узлами которого являются объекты терминального и нетерминального выражения.
  • Методы "Интерпретатора" в нетерминальных выражениях позволяют реализовать правила. При этом мы легко можем добавить новые правила, определив новые объекты нетерминального выражения со своей реализацией метода шаблона. Однако недостаток данного шаблона заключается в том, что он подходит только для тех случаев, когда правила относительно просты и не содержат большого количества ответвлений. В более сложных случаях следует выбирать другие способы проектирования приложения.
  • Жизненной аналогией этого шаблона является мясорубка, которая способна по определенным правилам преобразовывать небольшой входной набор кусков различного мяса к определенному виду фарша (разного состава). Такая мясорубка является достаточно надежным и универсальным инструментом, если на вход ей не подавать что-то совсем плотное и жесткое.

    (рис 6.2.1) Шаблон "Интерпретатор"

    Итератор

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

    В качестве основного назначения паттерна следует выделить:

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

    Алгоритм реализации данного шаблона предписывает следующие стадии:

  • Создается определенный класс ("Итератор"), который определяет интерфейс для доступа и перебора элементов.
  • Конкретный экземпляр класса "Итератор" реализует его интерфейс и следит за текущей позицией при обходе агрегата.
  • Агрегат определяет интерфейс для создания объекта-итератора.
  • Конкретный экземпляр агрегата реализует интерфейс создания итератора и возвращает экземпляр его класса.
  • Конкретный итератор отслеживает текущий объект в агрегате и может вычислить следующий объект при переборе.
  • Таким образом, из коллекции данных "выносится" функциональность обхода элементов и ей придается статус объекта. Это приводит к тому, что:

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

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

    (рис 6.2.2) Шаблон "Итератор"

    Команда (Транзакция)

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

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

    При реализации шаблона "Команда" следует обратить внимание на следующие моменты:

  • Насколько "умной" должна быть "Команда". У "Команды" может быть широкий круг обязанностей. С одной стороны, простое определение связи между получателем и действиями, которые нужно выполнить для удовлетворения запроса, с другой – независимая команда, т.е. реализация всего самостоятельно, без обращения за помощью к получателю. Последний вариант полезен, когда вы хотите определить команды, не зависящие от реализованных в системе классов, когда подходящего получателя не существует или когда получатель команде точно не известен.
  • Поддержка отмены и повтора операций. Команды могут поддерживать отмену и повтор операций, если имеется возможность отменить результаты выполнения. Как правило, вся необходимая для этого информация сохраняется, в том числе:
  • объект-получатель, который выполняет операции в ответ на запрос;
  • аргументы операции, выполненной получателем;
  • Исходные значения различных атрибутов получателя, которые могли измениться в результате обработки запроса.
  • Получатель должен предоставить операции, позволяющие команде вернуться в исходное состояние. Для поддержки всего одного уровня отмены приложению достаточно сохранять только последнюю выполненную команду. Если же нужны многоуровневые отмена и повтор операций, то придется вести список истории выполненных команд. Максимальная длина этого списка определяет число уровней отмены и повтора. Проход по списку в обратном направлении и откат результатов всех встретившихся по пути команд отменяет их действие. Проход в прямом направлении и выполнение встретившихся команд приводит к повтору выполнения действий.

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

  • Любое приложение c возможностями отмены или повторения действий (undo/redo) пользователя.
  • Сетевые распределенные системы, использующие запросы в виде объектов в качестве основного примитива инициализации каких-либо операций.
  • Системы с поддержкой асинхронных вызовов, инкапсулирующие обратный вызов в виде опроса объекта.
  • Перечислять такие задачи можно бесконечно, важно понять, что шаблон "Команда"– один из самых распространенных шаблонов проектирования. Аналогией данного шаблона является полет на борту самолета. После того как самолет взлетел, есть только два возможных дальнейших пути–попасть в точку назначения или вернуться в точку вылета. Все остальные действия, которые проходят по ходу полета,не рассматриваются в отрыве от самого перелета и являются необходимыми составляющими для его завершения.

    Наблюдатель

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

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

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

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

    Шаблон "Наблюдатель" можно охарактеризовать следующими отличительными чертами:

  • Шаблон "Наблюдатель" скрывает главный компонент в объект-абстракцию, а изменяемые компоненты в иерархию наблюдателей.
  • "Наблюдатель" определяет часть представление в рассмотренном ранее архитектурном шаблонеModel-View-Controller (MVC).
  • Он находит широкое применение в системах пользовательского интерфейса, в которых данные и их представления ("виды") отделены друг от друга:
  • При изменении данных должны быть изменены все представления этих данных (например, в виде таблицы, графика и диаграммы).
  • Для реализации шаблона наблюдатель необходимо:

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

  • Минимальная связанность объекта и наблюдателя.
  • Объект знает лишь о том, что у него есть ряд наблюдателей.
  • Широковещательность оповещения.
  • Объект оповещает не конкретного, а всех подписанных на него наблюдателей.
  • Недостатками являются:

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

    "Не разговаривайте с неизвестным" – один из самых неизвестных шаблонов проектирования, задача которого состоит в снижении монолитности и повышении гибкости системы за счет снижения зависимости объекта от косвенных компонентов системы, о которых ему напрямую ничего не известно.

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

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

    В конечном итоге, использование шаблона "Не разговаривайте с неизвестным" позволит обеспечить устойчивость системы к изменению структуры отдельных объектов.

    Посетитель

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

    Компонент, использующий данный паттерн:

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

    Главными преимуществами применения этого паттерна являются следующие:

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

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

    Посредник

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

    "Спагетти-код"– низкоструктурированная, запутанная и трудная для понимания программа. "Спагетти-код" назван так, потому что ход выполнения программы похож на миску спагетти–извилистый и запутанный.

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

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

    Для реализации шаблона "Посредник" потребуется:

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

    Для того чтобы внедрение шаблона "Посредник" в программу было выполнено наиболее эффективным образом, необходимо предварительно:

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

  • снизить связанность между коллегами;
  • централизовать управление системой: этот шаблон заменяет способ взаимодействия " все со всеми " на взаимодействие "один со всеми";
  • упростить внедрение новой функциональности благодаря слабой связанности кода.
  • Недостатки же его использования следующие:

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

    Состояние

    В ситуациях, когда требуется варьировать поведение объекта в зависимости от его внутреннего состояния, используют шаблон проектирования cодноименным названием–"Состояние".

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

    Структура этого паттерна определяется следующим образом:

  • Определенный класс (1)должен содержать определение внешнего интерфейса и хранить внутри себя ссылку на текущее состояние объекта.
  • Интерфейс абстрактного базового класса (2)должен повторять интерфейс класса (1) за исключением одного дополнительного параметра – указателя на экземпляр (1).
  • Производные от (2) класса определяют поведение, специфичное для конкретного состояния.
  • Класс "Обертка"(1) делегирует все полученные запросы объекту "Текущее состояние", который может использовать полученный дополнительный параметр для доступа к экземпляру класса (1).
  • Реализацию паттерна "Состояние" можно организовать следующим способом:

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

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

    (рис 6.2.8) Шаблон "Состояние"

    Стратегия

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

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

    Для реализации данного шаблона потребуется:

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

    К основным преимуществам использования этого шаблона следует отнести следующие:

  • Оперативная замена функциональных алгоритмов, выполняющих определенную обязанность.
  • Трансформация структуры программного обеспечения в сторону делегирования определенных обязанностей.
  • Реализует принцип инкапсуляции.
  • Скрывает опасные/лишние данные.
  • Если провести "жизненную" аналогию по применению этого шаблона, то уместным будет следующее сравнение:

  • Вам нужно добраться до определенного пункта.
  • Можно доехать различным транспортом (автобус, такси, велосипед и пр.).
  • Вид транспорта – стратегия достижения конечного пункта.
  • Вы выбираете конкретную стратегию в зависимости от контекста (наличия денег, времени и т.д.).
  • Одна стратегия может быть изменена на другую в процессе выполнения, при корректировке имеющихся ресурсов.
  • (рис 6.2.9) Шаблон "Стратегия"

    Хранитель

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

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

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

    Особенно актуально применение этого паттерна в ситуации, когда нужно восстановить объект обратно в прежнее состояние.

    Для реализации данного шаблона обязательно должны быть определены 3 различных "участника":

  • Хозяин. Объект, умеющий создавать "Хранителя", а также знающий, как восстановить свое внутреннее состояние из "Хранителя".
  • Смотритель. Объект, который знает, почему и когда "Хозяин" должен сохранять и восстанавливать себя.
  • Хранитель. Объект, который пишется и читается "Хозяином", за которым присматривает "Смотритель".
  • Алгоритм выполнения шаблона "Хранитель" определяется следующим образом:

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

    Шаблон проектирования "Хранитель" фиксирует и сохраняет за пределами объекта "Хранителя" его внутреннее состояние так, чтобы позже этот объект можно было бы восстановить в таком же состоянии.

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

    Хорошей жизненной аналогией данного паттерна является коробка с паззлом, на передней части которой изображена картина в целом. Именно напоминание картины в целом –это "Хранитель" конечного состояния объекта.

    Цепочка обязанностей

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

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

    С одной стороны, все прозрачно и понятно–есть множество типов сообщений, есть множество обработчиков этих сообщений. Задача системы состоит в том, чтобы обрабатывать каждое сообщение в потоке ассоциированным с его конкретным типом обработчика.

    Паттерн "Цепочка обязанностей" позволяет реализовать эффективный механизм увязывания типов сообщений и обработчиков.

    Если в системе небольшое число обработчиков, то достаточно реализовать данный механизм на базе цикличной конструкции (if, case, whileи пр.). Однако по мере роста и развития систем появляются дополнительные значимые нюансы:

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

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

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

    "Жизненным" примером этого шаблона является компьютерная сеть–большое количество обработчиков, узлов сети (компьютеров, серверов, маршрутизаторов) и еще большее количество типов сетевых запросов:

  • Сеть передает запрос на свои узлы.
  • Маршрутизатор передает запрос конкретному обработчику.
  • Сервер обрабатывает запрос.
  • (рис 6.2.11) Шаблон "Цепочка обязанностей"

    Шаблонный метод

    Когда имеются два разных, но в тоже время очень похожих компонента и требуется внести изменения в оба компонента, избежав при этом вредоносного дублирования кода, применяется паттерн "Шаблонный метод".

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

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

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

    Строители зданий используют паттерн "Шаблонный метод" при проектировании новых домов. Используются типовые, хорошо себя зарекомендовавшие планы, в которых модифицируются отдельные части, не влияющие на основные архитектурные компоненты.

    Высокое зацепление

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

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

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

  • Зацепление – мера связанности и сфокусированности обязанностей класса.
  • Элемент обладает высокой степенью зацепления, если его обязанности тесно связаны между собой, и он не выполняет огромных объемов работы. Класс с низкой степенью зацепления выполняет много разнородных функций или несвязанных между собой обязанностей.

    Реализация шаблона "Высокое зацепление" позволяет легко модифицировать и сопровождать программное обеспечение, повышает возможность его повторного использования.

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

    Принципы реализации высокого зацепления утверждают, что класс должен стараться выполнять как можно меньше неспецифичных для него задач и иметь вполне определенную область применения. Считается что связывание и зацепление –это "инь" и "янь" проектирования ПО. Некорректное связывание порождает неправильное зацепление, и наоборот.

    Контроллер

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

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

    "Контроллер"– это объект, который отвечает за обработку системных событий и не относится к интерфейсу пользователя. "Контроллер" определяет методы выполнения системных операций.

    Паттерн "Контроллер" призван решить наболевшую проблему разделения интерфейса и логики в интерактивном приложении.

    Если вспомнить изученный ранее архитектурный шаблон MVC, то шаблон проектирования "Контроллер" отвечает за организацию компоненты, скрывающейся под символом "С":

  • Вызовы из слоя представления "V" передаются в контроллер "C".
  • Контроллер, в зависимости от специфики данных и деталей их обработки, маршрутизирует вызовы определенной модели "M".
  • Системный компонент, реализуемый по шаблону "Контроллер", должен удовлетворять следующим условиям:

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

    Шаблон "Контроллер" является не объектом автоматизации предметной области, а искусственной конструкцией, которая выполняется в соответствии с четко определенными правилами.

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

    Полиморфизм

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

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

    Перенаправление

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

    Наиболее очевидным вариантом является делегирование обязанности обеспечения связи между службами или компонентами специализированному промежуточному объекту. Рассматриваемый шаблон "Перенаправление" разработан для решения задачи прямой связности.

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

    Заключение об поведенческих шаблонах

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

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

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

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

    Страницы:

    Введение

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

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

    Шаблоны проектирования поведения объектов

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

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

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

    Интерпретатор

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

    В этом случае целесообразно разработать шаблон "Интерпретатор", который решает данную задачу.

    "Интерпретатор" (англ. Interpreter) – поведенческий шаблон проектирования, решающий часто встречающуюся, но подверженную изменениям задачу. Также известен как Little (Small) Language.

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

  • Абстрактное выражение.
  • Определяет интерфейс выражения, объявляет метод шаблона.
  • Терминальное выражение.
  • Реализует методы. Для каждого объекта создается свое терминальное выражение.
  • Нетерминальное выражение.
  • Представляет правило. Для каждого отдельного правила создается свой объект нетерминального выражения.
  • Контекст.
  • Содержит общую информацию. Может использоваться объектами терминальных и нетерминальных выражений для сохранения состояния операций и последующего доступа к сохраненному состоянию.
  • Клиент.
  • Управляет выполнением бизнес-логики в виде абстрактного синтаксического дерева, узлами которого являются объекты терминального и нетерминального выражения.
  • Методы "Интерпретатора" в нетерминальных выражениях позволяют реализовать правила. При этом мы легко можем добавить новые правила, определив новые объекты нетерминального выражения со своей реализацией метода шаблона. Однако недостаток данного шаблона заключается в том, что он подходит только для тех случаев, когда правила относительно просты и не содержат большого количества ответвлений. В более сложных случаях следует выбирать другие способы проектирования приложения.
  • Жизненной аналогией этого шаблона является мясорубка, которая способна по определенным правилам преобразовывать небольшой входной набор кусков различного мяса к определенному виду фарша (разного состава). Такая мясорубка является достаточно надежным и универсальным инструментом, если на вход ей не подавать что-то совсем плотное и жесткое.

    (рис 6.2.1) Шаблон "Интерпретатор"

    Итератор

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

    В качестве основного назначения паттерна следует выделить:

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

    Алгоритм реализации данного шаблона предписывает следующие стадии:

  • Создается определенный класс ("Итератор"), который определяет интерфейс для доступа и перебора элементов.
  • Конкретный экземпляр класса "Итератор" реализует его интерфейс и следит за текущей позицией при обходе агрегата.
  • Агрегат определяет интерфейс для создания объекта-итератора.
  • Конкретный экземпляр агрегата реализует интерфейс создания итератора и возвращает экземпляр его класса.
  • Конкретный итератор отслеживает текущий объект в агрегате и может вычислить следующий объект при переборе.
  • Таким образом, из коллекции данных "выносится" функциональность обхода элементов и ей придается статус объекта. Это приводит к тому, что:

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

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

    (рис 6.2.2) Шаблон "Итератор"

    Команда (Транзакция)

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

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

    При реализации шаблона "Команда" следует обратить внимание на следующие моменты:

  • Насколько "умной" должна быть "Команда". У "Команды" может быть широкий круг обязанностей. С одной стороны, простое определение связи между получателем и действиями, которые нужно выполнить для удовлетворения запроса, с другой – независимая команда, т.е. реализация всего самостоятельно, без обращения за помощью к получателю. Последний вариант полезен, когда вы хотите определить команды, не зависящие от реализованных в системе классов, когда подходящего получателя не существует или когда получатель команде точно не известен.
  • Поддержка отмены и повтора операций. Команды могут поддерживать отмену и повтор операций, если имеется возможность отменить результаты выполнения. Как правило, вся необходимая для этого информация сохраняется, в том числе:
  • объект-получатель, который выполняет операции в ответ на запрос;
  • аргументы операции, выполненной получателем;
  • Исходные значения различных атрибутов получателя, которые могли измениться в результате обработки запроса.
  • Получатель должен предоставить операции, позволяющие команде вернуться в исходное состояние. Для поддержки всего одного уровня отмены приложению достаточно сохранять только последнюю выполненную команду. Если же нужны многоуровневые отмена и повтор операций, то придется вести список истории выполненных команд. Максимальная длина этого списка определяет число уровней отмены и повтора. Проход по списку в обратном направлении и откат результатов всех встретившихся по пути команд отменяет их действие. Проход в прямом направлении и выполнение встретившихся команд приводит к повтору выполнения действий.

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

  • Любое приложение c возможностями отмены или повторения действий (undo/redo) пользователя.
  • Сетевые распределенные системы, использующие запросы в виде объектов в качестве основного примитива инициализации каких-либо операций.
  • Системы с поддержкой асинхронных вызовов, инкапсулирующие обратный вызов в виде опроса объекта.
  • Перечислять такие задачи можно бесконечно, важно понять, что шаблон "Команда"– один из самых распространенных шаблонов проектирования. Аналогией данного шаблона является полет на борту самолета. После того как самолет взлетел, есть только два возможных дальнейших пути–попасть в точку назначения или вернуться в точку вылета. Все остальные действия, которые проходят по ходу полета,не рассматриваются в отрыве от самого перелета и являются необходимыми составляющими для его завершения.

    Наблюдатель

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

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

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

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

    Шаблон "Наблюдатель" можно охарактеризовать следующими отличительными чертами:

  • Шаблон "Наблюдатель" скрывает главный компонент в объект-абстракцию, а изменяемые компоненты в иерархию наблюдателей.
  • "Наблюдатель" определяет часть представление в рассмотренном ранее архитектурном шаблонеModel-View-Controller (MVC).
  • Он находит широкое применение в системах пользовательского интерфейса, в которых данные и их представления ("виды") отделены друг от друга:
  • При изменении данных должны быть изменены все представления этих данных (например, в виде таблицы, графика и диаграммы).
  • Для реализации шаблона наблюдатель необходимо:

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

  • Минимальная связанность объекта и наблюдателя.
  • Объект знает лишь о том, что у него есть ряд наблюдателей.
  • Широковещательность оповещения.
  • Объект оповещает не конкретного, а всех подписанных на него наблюдателей.
  • Недостатками являются:

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

    "Не разговаривайте с неизвестным" – один из самых неизвестных шаблонов проектирования, задача которого состоит в снижении монолитности и повышении гибкости системы за счет снижения зависимости объекта от косвенных компонентов системы, о которых ему напрямую ничего не известно.

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

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

    В конечном итоге, использование шаблона "Не разговаривайте с неизвестным" позволит обеспечить устойчивость системы к изменению структуры отдельных объектов.

    Посетитель

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

    Компонент, использующий данный паттерн:

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

    Главными преимуществами применения этого паттерна являются следующие:

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

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

    Посредник

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

    "Спагетти-код"– низкоструктурированная, запутанная и трудная для понимания программа. "Спагетти-код" назван так, потому что ход выполнения программы похож на миску спагетти–извилистый и запутанный.

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

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

    Для реализации шаблона "Посредник" потребуется:

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

    Для того чтобы внедрение шаблона "Посредник" в программу было выполнено наиболее эффективным образом, необходимо предварительно:

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

  • снизить связанность между коллегами;
  • централизовать управление системой: этот шаблон заменяет способ взаимодействия " все со всеми " на взаимодействие "один со всеми";
  • упростить внедрение новой функциональности благодаря слабой связанности кода.
  • Недостатки же его использования следующие:

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

    Состояние

    В ситуациях, когда требуется варьировать поведение объекта в зависимости от его внутреннего состояния, используют шаблон проектирования cодноименным названием–"Состояние".

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

    Структура этого паттерна определяется следующим образом:

  • Определенный класс (1)должен содержать определение внешнего интерфейса и хранить внутри себя ссылку на текущее состояние объекта.
  • Интерфейс абстрактного базового класса (2)должен повторять интерфейс класса (1) за исключением одного дополнительного параметра – указателя на экземпляр (1).
  • Производные от (2) класса определяют поведение, специфичное для конкретного состояния.
  • Класс "Обертка"(1) делегирует все полученные запросы объекту "Текущее состояние", который может использовать полученный дополнительный параметр для доступа к экземпляру класса (1).
  • Реализацию паттерна "Состояние" можно организовать следующим способом:

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

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

    (рис 6.2.8) Шаблон "Состояние"

    Стратегия

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

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

    Для реализации данного шаблона потребуется:

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

    К основным преимуществам использования этого шаблона следует отнести следующие:

  • Оперативная замена функциональных алгоритмов, выполняющих определенную обязанность.
  • Трансформация структуры программного обеспечения в сторону делегирования определенных обязанностей.
  • Реализует принцип инкапсуляции.
  • Скрывает опасные/лишние данные.
  • Если провести "жизненную" аналогию по применению этого шаблона, то уместным будет следующее сравнение:

  • Вам нужно добраться до определенного пункта.
  • Можно доехать различным транспортом (автобус, такси, велосипед и пр.).
  • Вид транспорта – стратегия достижения конечного пункта.
  • Вы выбираете конкретную стратегию в зависимости от контекста (наличия денег, времени и т.д.).
  • Одна стратегия может быть изменена на другую в процессе выполнения, при корректировке имеющихся ресурсов.
  • (рис 6.2.9) Шаблон "Стратегия"

    Хранитель

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

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

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

    Особенно актуально применение этого паттерна в ситуации, когда нужно восстановить объект обратно в прежнее состояние.

    Для реализации данного шаблона обязательно должны быть определены 3 различных "участника":

  • Хозяин. Объект, умеющий создавать "Хранителя", а также знающий, как восстановить свое внутреннее состояние из "Хранителя".
  • Смотритель. Объект, который знает, почему и когда "Хозяин" должен сохранять и восстанавливать себя.
  • Хранитель. Объект, который пишется и читается "Хозяином", за которым присматривает "Смотритель".
  • Алгоритм выполнения шаблона "Хранитель" определяется следующим образом:

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

    Шаблон проектирования "Хранитель" фиксирует и сохраняет за пределами объекта "Хранителя" его внутреннее состояние так, чтобы позже этот объект можно было бы восстановить в таком же состоянии.

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

    Хорошей жизненной аналогией данного паттерна является коробка с паззлом, на передней части которой изображена картина в целом. Именно напоминание картины в целом –это "Хранитель" конечного состояния объекта.

    Цепочка обязанностей

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

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

    С одной стороны, все прозрачно и понятно–есть множество типов сообщений, есть множество обработчиков этих сообщений. Задача системы состоит в том, чтобы обрабатывать каждое сообщение в потоке ассоциированным с его конкретным типом обработчика.

    Паттерн "Цепочка обязанностей" позволяет реализовать эффективный механизм увязывания типов сообщений и обработчиков.

    Если в системе небольшое число обработчиков, то достаточно реализовать данный механизм на базе цикличной конструкции (if, case, whileи пр.). Однако по мере роста и развития систем появляются дополнительные значимые нюансы:

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

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

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

    "Жизненным" примером этого шаблона является компьютерная сеть–большое количество обработчиков, узлов сети (компьютеров, серверов, маршрутизаторов) и еще большее количество типов сетевых запросов:

  • Сеть передает запрос на свои узлы.
  • Маршрутизатор передает запрос конкретному обработчику.
  • Сервер обрабатывает запрос.
  • (рис 6.2.11) Шаблон "Цепочка обязанностей"

    Шаблонный метод

    Когда имеются два разных, но в тоже время очень похожих компонента и требуется внести изменения в оба компонента, избежав при этом вредоносного дублирования кода, применяется паттерн "Шаблонный метод".

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

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

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

    Строители зданий используют паттерн "Шаблонный метод" при проектировании новых домов. Используются типовые, хорошо себя зарекомендовавшие планы, в которых модифицируются отдельные части, не влияющие на основные архитектурные компоненты.

    Высокое зацепление

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

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

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

  • Зацепление – мера связанности и сфокусированности обязанностей класса.
  • Элемент обладает высокой степенью зацепления, если его обязанности тесно связаны между собой, и он не выполняет огромных объемов работы. Класс с низкой степенью зацепления выполняет много разнородных функций или несвязанных между собой обязанностей.

    Реализация шаблона "Высокое зацепление" позволяет легко модифицировать и сопровождать программное обеспечение, повышает возможность его повторного использования.

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

    Принципы реализации высокого зацепления утверждают, что класс должен стараться выполнять как можно меньше неспецифичных для него задач и иметь вполне определенную область применения. Считается что связывание и зацепление –это "инь" и "янь" проектирования ПО. Некорректное связывание порождает неправильное зацепление, и наоборот.

    Контроллер

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

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

    "Контроллер"– это объект, который отвечает за обработку системных событий и не относится к интерфейсу пользователя. "Контроллер" определяет методы выполнения системных операций.

    Паттерн "Контроллер" призван решить наболевшую проблему разделения интерфейса и логики в интерактивном приложении.

    Если вспомнить изученный ранее архитектурный шаблон MVC, то шаблон проектирования "Контроллер" отвечает за организацию компоненты, скрывающейся под символом "С":

  • Вызовы из слоя представления "V" передаются в контроллер "C".
  • Контроллер, в зависимости от специфики данных и деталей их обработки, маршрутизирует вызовы определенной модели "M".
  • Системный компонент, реализуемый по шаблону "Контроллер", должен удовлетворять следующим условиям:

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

    Шаблон "Контроллер" является не объектом автоматизации предметной области, а искусственной конструкцией, которая выполняется в соответствии с четко определенными правилами.

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

    Полиморфизм

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

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

    Перенаправление

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

    Наиболее очевидным вариантом является делегирование обязанности обеспечения связи между службами или компонентами специализированному промежуточному объекту. Рассматриваемый шаблон "Перенаправление" разработан для решения задачи прямой связности.

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

    Заключение об поведенческих шаблонах

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

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

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

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

    Вернуться к учебному плану