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

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

#Переключатель в кириллицу. (32810 hits)
#"C# и платформа .NET" Эндрю Троелсен (Andrew Troelsen, "C# and the .NET platform"), листинги, код, примеры из книги, исходники. (38879 hits)
#Переворот символов строки (или элементов одномерного массива). (112205 hits)
#Шифрование произвольных данных. (328763 hits)
#Масштабирование, пропорциональное изменение размеров картинки. (100906 hits)
#Создание нестандартного (custom-ного) окна браузера. (35918 hits)
#Сглаживание кривой В-сплайном. (38768 hits)
#Сортировка выбором, общий подход. (72844 hits)
#Использование компилируемых (prepared) запросов. (30659 hits)
#Динамическая очистка выпадающего списка (select) на javascript. (90861 hits)
#Вычисление двойного интеграла с использованием MPI. (60332 hits)
#"Липкие" окна. (32174 hits)
#Полезные утилиты, небольшие api и библиотеки и проч.. (69732 hits)
#Программное создание ссылок. (99851 hits)
#Загрузчик классов. (43450 hits)
#Рисование Фрактала (листьев папоротника). (53153 hits)
#Вычисление среднего, среднего отклонения, среднеквадратического отклонения и дисперсии заданной выборки. (46433 hits)
#Сапер. (53392 hits)
#Вращение фигуры в плоскости. (40047 hits)
#Поразрядная сортировка, общий принцип. (130611 hits)


Главная >> Каталог задач >> Паттерны >> Структурные >> Мост (Bridge)

Мост (Bridge)

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

Имя

«Паттерн
Bridge»

Мост – паттерн, оптимальным образом структурирующий используемые иерархии общих абстракций и их конкретных реализаций. Известен также под именем Handle/Body (описатель/тело).

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

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

Мотивация

Рассмотрим реализацию переносимой (повторно используемой) абстракции окна в библиотеке для разработки пользовательских интерфейсов. Написанные с ее помощью приложения должны работать в разных средах, например под X Window System и Presentation Manager (PM) от компании IBM. С помощью наследования мы могли бы определить абстрактный класс Window и его подклассы XWindow и PMWindow, реализующие интерфейс окна для разных платформ. Но у такого решения есть два недостатка:
  1. Неудобно распространять абстракцию Window на другие виды окон или новые платформы. Представьте себе абстрактный подкласс IconWindow, который специализирует (уточняет) абстракцию окна для пиктограмм. Чтобы поддержать пиктограммы на обеих платформах, нам придется реализовать два новых подкласса XlconWindow и PMIconWindow. И так далее по два подкласса необходимо будет также определять для каждого нового вида окон (DialogWindow) впоследствии. А для поддержки третьей платформы придется вообще для всех видов окон (Window, IconWindow, DialogWindow) определять по новому подклассу.
  2. Клиентский код становится платформенно-зависимым. При создании окна клиент инстанцирует конкретный класс, имеющий вполне определенную реализацию. Например, создавая объект XWindow, мы привязываем абстракцию окна к ее реализации для системы X Window и, следовательно, делаем код клиента ориентированным именно на эту оконную систему. Таким образом, усложняется перенос клиента на другие платформы.
    Клиенты должны иметь возможность создавать окно, не привязываясь к конкретной реализации. Только сама реализация окна должна зависеть от платформы, на которой работает приложение. Поэтому в клиентском коде не может быть никаких упоминаний о платформах.
    Паттерн абстрактная фабрика конечно решил бы эту проблему, но от п.1 все равно не избавил бы, а наоборот усложнил бы проблему введения новых видов окон.
С помощью паттерна мост эти проблемы решаются. Абстракция окна и ее реализация помещаются в раздельные иерархии классов. Таким образом, существует одна иерархия для абстрактных классов окон (Window, IconWindow, TransientWindow) и другая  (с корнем WindowImp) - для платформенно-зависимых конкретных реализаций. Так, подкласс XWindowImp предоставляет реализацию в системе X Window System.

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

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

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

Решение

Участники паттерна Мост (Bridge)

  1. Abstraction (Window) – абстракция.
    Определяет интерфейс абстракции и агрегирует (хранит ссылку) на объект типа Implement.
  2. RefinedAbstraction (IconWindow) - уточненная абстракция.
    Более специфичный подкласс основной абстракции, расширяет интерфейс, определенный абстракцией Abstraction.
  3. Implementor (WindowImp) – исполнитель.
    Определяет интерфейс для классов реализации. Он не обязан точно соответствовать интерфейсу класса Abstraction, более того оба интерфейса могут быть совершенно различны. Обычно интерфейс класса Implementor предоставляет только примитивные операции, а класс Abstraction определяет операции более высокого уровня, базирующиеся на этих примитивах.
  4. ConcreteImplementor (XWindowlmp, PMWindowlmp) – один из конкретных (платформо-зависимых) исполнителей.
    Содержит конкретную реализацию интерфейса класса Implementor, т.е. по-своему реализует примитивы, определенные в Implementor-е.

Схема использования паттерна Мост (Bridge)

Объект Abstraction решает свои задачи, реализует свою логику, пользуюсь методами объекта класса Implementor (примитивами), ссылку на которого он получает когда-то ранее, перед началом своей работы.
 

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

Если вы предполагаете применить паттерн мост, то подумайте еще о таких вопросах реализации:
  1. Только один класс Implementor.
    В ситуациях, когда есть только одна реализация, создавать абстрактный класс Implementor необязательно. Это вырожденный случай паттерн мост – между классами Abstraction и Implementor существует взаимно-однозначное соответствие. Тем не менее разделение все же полезно, если нужно, чтобы изменение реализации класса не отражалось на существующих клиентах (должно быть достаточно заново скомпоновать программу, не перекомпилируя клиентский код).
    В C++ интерфейс класса Implementor можно определить в закрытом заголовочном файле, который не передается клиентам. Это позволяет полностью скрыть реализацию класса от клиентов.
  2. Cоздание правильного объекта Implementor.
    Как, когда и где принимается решение о том, какой из нескольких классов Implementor инстанцировать?
    Если у класса Abstraction есть информация о конкретных классах Concretelmplementor, то он может инстанцировать один из них в своем конструкторе; какой именно - зависит от переданных конструктору параметров.
    Так, если класс коллекции поддерживает несколько реализаций, то решение можно принять в зависимости от размера коллекции. Для небольших коллекций применяется реализация в виде связанного списка, для больших – в виде хэшированных таблиц.
    Другой подход - заранее выбрать реализацию по умолчанию, а позже изменять ее в соответствии с тем, как она используется. Например, если число элементов в коллекции становится больше некоторой условной величины, то мы переключаемся с одной реализации на другую, более эффективную.
    Можно также делегировать решение другому объекту. В примере с иерархиями Window/WindowImp уместно было бы ввести фабричный объект (см. паттерн абстрактная фабрика), единственная задача которого - инкапсулировать платформенную специфику. Фабрика обладает информацией, объекты Windowlmp какого вида надо создавать для данной платформы, а объект Window просто обращается к ней с запросом о предоставлении какого-нибудь объекта Windowlmp, при этом понятно, что объект получит то, что нужно. Преимущество описанного подхода: класс Abstraction становится вообще уже напрямую не привязан ни к одному из классов Implementor – а, как мы знаем, чем меньше связанность сущностей, тем более система становится гибкой.
  3. Некорректность использования множественного наследования
    В C++, например, для объединения интерфейса с его реализацией можно воспользоваться множественным наследованием. Например, класс может открыто наследовать классу Abstraction и закрыто – классу ConcreteImplementor. Но такое решение зависит от статического наследования и жестко привязывает реализацию к ее интерфейсу. Поэтому реализовать настоящий мост с помощью множественного наследования невозможно, по крайней мере в C++.

Результаты

Результаты применения паттерна мост:
  1. Отделение реализации от интерфейса.
    Реализация больше не имеет постоянной привязки к интерфейсу. Реализацию абстракции можно конфигурировать во время выполнения. Объект может даже динамически изменять свою реализацию.
    Разделение классов Abstraction и Implementor устраняет также зависимости от реализации, устанавливаемые на этапе компиляции. Чтобы изменить класс реализации, теперь вовсе не обязательно перекомпилировать класс Abstraction и его клиентов. Это свойство особенно важно, если необходимо обеспечить двоичную совместимость между разными версиями библиотеки классов.
    Кроме того, такое разделение облегчает разбиение системы на слои и тем самым позволяет улучшить ее структуру. Высокоуровневые части системы должны знать только о классах Abstraction и Implementor.
  2. Повышение степени расширяемости.
    Можно расширять независимо иерархии классов Abstraction и Implementor.
  3. Сокрытие деталей реализации от клиентов.
    Клиенты, пользующиеся интерфейсом Abstraction, изолируются от деталей реализации этих операций, которые на самом деле выполняются с помощью вызовов методов Implementor-а. Что делает возможным прозрачную подмену Implementor-ов как статически на этапе компиляции, так и динамически.

Пример

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

В качестве Abstraction выступает самый общий класс Command: Command.

Он агрегирует, хранит ссылку класс реализации команды CommandImpl (Implementor): CommandImpl.

Далее уточняем разновидности команд (RefinedAbstraction), как-то команды на удаление, добавление, редактирование. Например, можно выделить команды на редактирование и удаление: UpdateCommand, AddCommand.

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

Теперь пора перейти к реализации конкретных алгоритмов обработки данных видов команд. Для реализации этих 2-х команд в файловом контексте, т.е. по отношению к файлам, будет достаточно определить следующие ConcreteImplementator-ы: FileCommand, UpdateFileCommand, AddFileCommand.

Соответственно для редактирования файла, объект класса UpdateCommand (или Command) нужно будет сконфигурировать настроенным объектом UpdateFileCommand, а для добавления файла – объектом AddFileCommand.

Для работы с базой данной ConcreteImplementator-ы примут вид: SQLCommand, SQLUpdateCommand.

Заметьте, что не приводится специальная реализация для команды добавления – в данном случае можно использовать ту же SQLUpdateCommand, т.к. все необходимые процедуры она выполняет (по крайней мере, они не отличаются от случая добавления – разница будет лишь в тексте SQL-запроса). И в этом еще одна гибкость паттерна мост – в большинстве случаев нам не приходится выстраивать полные строго параллельные иерархии реализаций для всех абстракций, т.к. часто одни и те же ConcreteImplementor-ы удовлетворяют нескольким RefinedAbstraction-нам.
 
Все, - теперь клиенты нашей системы могут свободно запускать данные команды добавления и редактирования в контекстах файлов или базы данных: Client.
Обратите на код в методе main – это то, что и достигается применением данного паттерна: независимый, единый код запуска любых видов команд в любом контексте, который не нужно будет менять и перекомпилировать при каких-либо изменениях в деталях реализации тех или иных операций, алгоритмов выполнения команд и т.д.
Конкретные реализации clientUpdateImpl и balanceCorrectImpl для простоты здесь определяются напрямую, но как говорилось выше часто бывает полезно получать их динамически в конструкторе команды (Command) из абстрактной фабрики.

 

Известные применения паттерна Мост (Bridge)

Пример класса Window позаимствован из ЕТ++. В ЕТ++ класс WindowImp называется WindowPort и имеет такие подклассы, как XWindowPort и SunWindowPort. Объект Window создает соответствующего себе реализатора Implementor, запрашивая его у абстрактной фабрики, которая называется WindowSystem. Эта фабрика предоставляет интерфейс для создания платформенно-зависимых объектов: шрифтов, курсоров, растровых изображений и т.д.
Дизайн классов Window/WindowPort в ЕТ++ обобщает паттерн мост в том отношении, что WindowPort сохраняет также обратную ссылку на Window. Класс-реализатор WindowPort использует эту ссылку для извещения Window о событиях, специфичных для WindowPort: поступлений событий ввода, изменениях размера окна и т.д.
В работах Джеймса Коплиена и Бьерна Страуструпа упоминаются классы описателей и приводятся некоторые примеры. Основной акцент в этих примерах сделан на вопросах управления памятью, например разделении представления строк и поддержке объектов переменного размера. Нас же в первую очередь интересует поддержка независимых расширений абстракции и ее реализации.
В библиотеке libg++ определены классы, которые реализуют универсальные структуры данных: Set (множество), LinkedSet (множество как связанный список), HashSet (множество какхэш-таблица), LihkedList (связанный список) и HashTable (хэш-таблица). Set - это абстрактный класс, определяющий абстракцию множества, a LinkedList и HashTable - конкретные реализации связанного списка и хэш-таблицы. LinkedSet и HashSet - реализаторы абстракции Set, перекидывающие мост между Set и LinkedList и HashTable соответственно. Перед вами пример вырожденного моста, поскольку абстрактного класса Implement or здесь нет.
В библиотеке NeXT AppKit паттерн мост используется при реализации и отображении графических изображений. Рисунок может быть представлен по-разному. Оптимальный способ его отображения на экране зависит от свойств дисплея и прежде всего от числа цветов и разрешения. Если бы не AppKit, то для каждого приложения разработчикам пришлось бы самостоятельно выяснять, какой реализацией пользоваться в конкретных условиях.

AppKit предоставляет мост [google=NXImage NXImageRep]NXImage/NXImageRep[google]. Класс NXImage определяет интерфейс для обработки изображений. Реализация же определена в отдельной иерархии классов NXImageRep, в которой есть такие подклассы, как NXEPSImageRep, NXCachedlmageRep и NXBitMapImageRep. В классе NXImage хранятся ссылки на один или более объектов NXImageRep. Если имеется более одной реализации изображения, то NXImage выбирает самую подходящую для данного дисплея. При необходимости NXImage также может преобразовать изображение из одного формата в другой. Интересная особенность этого варианта моста в том, что NXImage может одновременно хранить несколько реализаций NXImageRep.

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

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

Реализации:

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

1) Command.java на java, code #442[автор:this]
2) CommandImpl.java на java, code #443[автор:this]
3) UpdateCommand.java на java, code #444[автор:this]
4) AddCommand.java на java, code #445[автор:this]
5) FileCommand.java на java, code #446[автор:this]
6) UpdateFileCommand.java на java, code #447[автор:this]
7) AddFileCommand.java на java, code #448[автор:this]
8) SQLCommand.java на java, code #449[автор:this]
9) SQLUpdateCommand.java на java, code #450[автор:this]
10) Client.java на java, code #451[автор:this]