Bridge

Назначение

Bridge предлагает разделить иерархию классов на две независимые, но взаимосвязанные структуры, одна из которых представляет абстракцию, а другая — реализацию и связать абстракцию с реализацией через композицию (has a).

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

Описание

Допустим, требуется реализовать функционал, который, в некотором аспекте может быть разложен на два раздельных, но взаимодействующих слоя абстракций. Реализации этих абстракций должны быть взаимно сочетаемы в любой комбинации. Например, необходимо реализовать функционал обработки данных с датчиков. Датчики сами по себе могут быть различных типов: простые, усредняющие, потоковые. В то же время датчики этих типов производятся различными компаниями, которые имеют свои особенности. Этот функционал раскладывается на два слоя абстракций: тип датчика и его производитель. В такой ситуации паттерн Bridge предлагает произвести отображение двух указанных слоев абстракций в соответствующие им иерархии классов. Между иерархиями устанавливается связь: экземпляры одной иерархии имеют ссылку на экземпляр другой, делегируя ей специфические операции. Связь между иерархиями как раз и образует “мост”.

Реализация

Abstraction
View
Абстракция, верхнего уровня, содержащая ссылку на экземпляр impl реализации
Implementation
MediaResource
Реализация, описываемая через общий интерфейс и предоставляющая некоторый функционал для абстракции
ConcreteAbstraction
LongFormView, ShortFormView
Конкретная абстракция, которая делегирует специфический функционал экземпляру конкретной реализации
ConcreteImplementation
ArtistMediaResource, BookMediaResource
Конкретная реализация некоторого функционала (часто адаптер для существующего объекта)

Проблема:

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

Примеры

Надо написать приложение (Spotify) в котором различные сущности могут быть отображены через некоторый набор представлений. Например, исполнители и книги могут быть показаны в короткой и подробной формах отображения:

Допустим, есть $ m $ вариантов отображения и $ n $ вариантов ресурсов. При прямом подходе необходимо написать для каждой комбинации отображения с ресурсом отдельный класс:

  • LongFormArtist
  • LongFormBook
  • ShotFormArtist
  • ShortFormView

Суммарно $ m \cdot n $ классов. Таким образом, кроме того что мы продублируем один и тот же код в разных местах, мы столкнемся с проблемой декартова произведения. Сложность расширения такой структуры с увеличением числа отображений/ресурсов растет всё быстрее.

Применив шаблон Bridge, число классов сокращается до $ m + n $. Для этого разделим структуру на два слоя абстракций — формы отображения и медиа ресурсы. Форма отображения будет абстракцией верхнего уровня, содержащая в себе реализацию, представленную конкретным медиа ресурсом и делегирующая этому медиа ресурсу некоторые специфические операции.

Варианты

  • Часто ConcreteImplementation в Bridge не реализует полностью самостоятельно интерфейс Implementation, а адаптирует существующий объект к этому интерфейсу. Например, ArtistMediaResource может адаптировать к интерфейсу MediaResource объект модели Artist. См. шаблон Adapter

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

Чем отличается

Decorator – Decorator, как и Bridge может решить проблему “class explosion”. Но есть принципиальный момент: Decorator работает на пространстве одной иерархии классов, а Bridge на пространстве двух иерархий. Т.е. Decorator позволяет комбинировать между собой классы из множества, допустим {1, 2, 3} получая (1, 2), (1, 2, 3), (1), (2, 3)... и т.д. а Bridge позволяет сочетать классы их одной иерархии с классами из другой, например, {A1, A1} с {B1, B2} получая (A1, B1), (A2, B1), .... Несмотря на то, что Decorator может декорировать различие реализации некоторого интерфейса, интерфейс декорируемого всё объекта равно одни. Bridge разделяет две иерархии с различными интерфейсами.

Strategy Bridge является логическим расширением шаблона Strategy, то есть частный случай Bridge c одним классом абстракции по сути и есть Strategy.

Adapter Bridge часто использует Adapter как часть (см. комментарий на UML диаграмме в разделе Реализация)

Справка

Декартово произведение: $ A\times B = \{(x,y)|x \in A, y \in B \} $

Мощность: $ |A\times B| = |A| \cdot |B| $

Ссылки

https://java-design-patterns.com/patterns/bridge/

https://github.com/iluwatar/java-design-patterns/tree/master/bridge

https://refactoring.guru/design-patterns/bridge

YouTube: Bridge Pattern – Design Patterns (ep 11)

Structural Patterns (comparison) – Design Patterns (ep 12)

Baeldung - The Bridge Pattern in Java