Также известен как Bridge

Мост

Суть паттерна

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

Проблема

Абстракция? Реализация?! Звучит пугающе! Чтобы понять о чём идёт речь, давайте разберём очень простой пример.

У вас есть класс геометрических Фигур, который имеет подклассы Квадрат и Треугольник. Вы хотите расширить иерархию фигур по цвету, то есть иметь Красные и Синие фигуры.

Чтобы всё это объединить, вам придётся создать 4 комбинации подклассов вроде СиниеКвадраты и КрасныеТреугольники.

Проблема паттерна Мост

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

Решение

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

Решение паттерна Мост

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

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

Абстракция и Реализация

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

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

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

Вы можете развивать программу в двух разных направлениях:

  • иметь несколько различных интерфейсов (например, для простых пользователей и администраторов).
  • поддерживать много видов API (например, работать под Windows, Linux и MacOS).

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

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

Паттерн Мост предлагает распутать клубок этого кода и выделить из него две части:

  • Абстракцию: слой GUI приложения.
  • Реализацию: слой взаимодействия с операционной системой.

Абстракция будет делегировать работу одному из объектов-Реализаций. Реализации можно будет взаимозаменять, если все они будут иметь общий интерфейс.

Вы сможете изменять интерфейс приложения, не трогая код работы с ОС. И наоборот, сможете добавить поддержку новой операционной системы, создав подкласс реализации, не трогая классы абстракции.

Структура

Схема структуры классов паттерна Мост
  1. Абстракция содержит управляющую логику. Код абстракции делегирует реальную работу связанному объекту-Реализации.

  2. Реализация задаёт общий интерфейс для всех реализаций. Все методы, которые здесь описаны, будут доступны Абстракции и её подклассам.

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

  3. Конкретные Реализации содержат платформо-зависимый код.

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

  5. Клиент работает только с объектами Абстракции, которая делегирует вызовы одной из Конкретных Реализаций.

Псевдокод

В этом примере Мост разделяет монолитный код приборов и пультов на две части:

  • Приборы (выступают реализацией)
  • Пульты управления ними (выступают абстракцией)
Схема примера паттерна Мост

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

Сами пульты можно развивать независимо от приборов. Например, можете создать подклассы пультов рассчитанных на две кнопки или сложные пульты с тач-интефрейсом.

Таким образом, паттерн Мост позволяет разбить одну сущность на несколько отдельных, а затем развивать их независимо друг от друга. Клиенту остаётся выбрать версию абстракции и реализации, с которым он хочет работать.

// Каждый класс пульта имеет ссылку на устройство, которым управляет. Методы
// пульта делегируют работу методам устройства.
class Remote is
    field device: Device
    constructor BasicRemote(device: Device) is
        this.device = device
    method togglePower() is
        if device.isEnabled() then device.disable()
        else device.enable()
    method volumeDown() is
        device.setVolume(device.getVolume() - 10)
    method volumeUp() is
        device.setVolume(device.getVolume() + 10)
    method channelDown() is
        device.setChannel(device.getChannel() - 1)
    method channelUp() is
        device.setChannel(device.getChannel() + 1)

// Вы можете расширять классы пультов не трогая код устройств.
class AdvancedRemote extends BasicRemote is
    method mute() is
        device.setVolume(0)


// Все устройства имеют общий интерфейс. Поэтому с ними может работать
// любой пульт.
interface Device is
    method isEnabled()
    method enable()
    method disable()
    method getVolume()
    method setVolume(percent)
    method getChannel()
    method setChannel(channel)

// Но каждое устройство может иметь особую реализацию.
class Tv implements Device is
    // ...

class Radio implements Device is
    // ...


// Где-то в клиентском коде.
tv = new Tv();
remote = new Remote(tv)
remote.pover()

radio = new Radio();
remote = new AdvancedRemote(radio)

Применимость

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

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

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

Класс нужно расширять в двух независимых плоскостях.

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

Вы хотите, чтобы реализацию можно было бы изменять во время выполнения программы.

Мост позволяет заменять реализацию даже во время выполнения программы, так как конкретная реализация не «вшита» в класс абстракции.

Кстати, из-за этого пункта Мост часто путают со Стратегией. Обратите внимания, что у Моста этот пункт стоит на последнем месте по значимости, так как его главная задача — структурная.

Шаги реализации

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

  2. Продумайте какие операции будут нужны Клиентам и опишите их в базовый класс Абстракции.

  3. Определите что могут все делать платформы и что потребуется Абстракции и создайте общий интерфейс Реализации.

  4. Для всех видов реализации создаёте собственные классы Конкретных реализаций. Убедитесь, что они имеют общий интерфейс.

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

  6. Если у вас есть несколько вариаций Абстракции, создайте для каждой из них свой подкласс.

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

Преимущества и недостатки

  • Платформо-независимость.
  • Реализует принцип открытости/закрытости.
  • Скрывает лишние или опасные детали реализации от клиента.
  • Усложняет код программы за счёт дополнительных классов.

Отношения с другими паттернами

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

  • Мост, Стратегия и Состояние (а также слегка и Адаптер) имеют схожие структуры классов — все они построены на принципе «композиции», то есть делегирования работы другим объектам. Тем не менее они отличаются тем, что решают разные проблемы. Помните, что паттерны — это не только рецепт построения кода определённым образом, но и описание проблем, которые привели к данному решению.

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

  • Паттерн Строитель может быть построен в виде Моста: директор будет играть роль абстракции, а строители — реализации.

Реализация в различных языках программирования

Java