Шаблон Мост позволяет разделить объект на абстракцию и реализацию так, чтобы они могли изменяться независимо друг от друга.
Данный шаблон используется если необходимо:
- независимо изменять интерфейс работы с клиентом и реализацию;
- (или) выбирать реализацию в процессе работы программы;
- (или) использовать одну реализацию в нескольких абстракциях;
- (или) уменьшить число классов, получающихся при использовании наследования.
Шаблон Мост предполагает, что основной код, необходимый для функционирования объекта, переносится в реализацию. Всё остальное, включая взаимодействие с клиентом, содержится в абстракции. Её методы, при необходимости, могут быть изменены или дополнены. Кроме того, она содержит экземпляр реализации и использует его для обработки поступающих от клиентов запросов. Под обработкой подразумевается как прямая переадресация запроса, так и вызов группы методов реализации для получения результата.
Если посмотреть внимательно, то видно, что результат разделения одного объекта практически идентичен результату применения Адаптера. Поэтому необходимо рассматривать применение шаблона Мост при проектировании или рефакторинге группы объектов, связанных наследованием. В этом случае, после разделения базового объекта, возможно:
- разрабатывать наследников реализации, отличающихся функциональностью;
- создавать наследников абстракции с уточненными или новыми методами, которые будут использовать уже готовые реализации;
- подставлять нужную реализацию в абстракцию при инициализации;
- заменять реализацию в процессе работы, изменяя функциональность объекта "на лету".
Лучше понять принцип разделения обязанностей между частями Моста можно используя платформенно-зависимые классы:
- в реализацию выносятся все платформенно-зависимые методы;
- всё остальное остается в абстракции;
- интерфейс между реализацией и абстракцией не должен полагаться на платформенно-зависимые данные и классы;
- используя наследование, создаются уточненные абстракции, которые могут содержать переработанные или новые методы и свойства;
- в дальнейшем достаточно замены класса реализации для того, чтобы перенести все абстракции на новую платформу.
Например, для иерархии графических примитивов реализацией может обеспечивать вывод точки на дисплей. Абстракцией – класс точки, а её уточнениями – классы линии, прямоугольника, круга и т.д. Все они используют исходную реализацию для рисования и для того, чтобы перенести их на новую платформу достаточно заменить реализацию.
Разумеется область применения Моста не ограничена аппаратно- и платформенно-зависимыми классами. В других случая можно Аналогично разделять функциональность других объектов.
Таким образом, можно выделить следующих участников шаблона:
- Абстракция (Abstraction) – определяет базовый интерфейс для работы с клиентом.
- Уточненная абстракция (Refined abstraction) – наследует абстракцию и вносит дополнительные свойства и методы.
- Реализация (Implementor) – определяет интерфейс реализаций.
- Конкретная реализация (Concrete implementor) – обеспечивает определенную функциональность.
Очень важным моментом в проектировании Моста является разработка двух интерфейсов: абстракции и её взаимодействия с реализацией. Чем меньше будет в них привязка к конкретной реализации, тем проще будет заменить её в дальнейшем. Например, использование для задания координаты класса Point из .NET усложнит последующий перенос на WinAPI.
При порождении экземпляра объекта, выбор конкретной реализации можно переложить на порождающие шаблоны Фабричный метод или Абстрактная фабрика. Кроме того, они же могут применяться для определения нужной клиенту уточненной абстракции.
Также стоит отметить, что использование шаблона Мост уменьшает число создаваемых классов. В приведенном выше примере, для поддержки новой платформы достаточно добавить только один класс – новую реализацию отображения точки. А при использовании только наследования, необходимо будет разработать аналоги всех исходных классов. Разница хорошо видна на схеме.
Кроме того, в случае с наследованием есть проблема добавления новых методов в подклассы. Такие методы не будут доступны через родительский интерфейс. Значит придется создавать новый, чтобы можно было подставить, например, LineNET вместо LineAPI и не потерять доступ к этим методам. При применении шаблона Мост такой проблемы нет в принципе, т.к. класс Line всего один.
Реализация шаблона в общем виде
- определяем интерфейс абстракции: разрабатываем его исходя из задачи
или берем интерфейс готового класса; - определяем функции реализации и её интерфейс (может не совпадать с интерфейсом абстракции);
- создаем реализацию, наследующую этот интерфейс;
- разрабатываем абстракцию;
- абстракция создает и содержит экземпляр реализации и использует его для обслуживания вызовов своих методов;
- создаем уточнения абстракции и новые версии реализации исходя из задачи;
- при создании абстракции она создает или получает нужную реализацию;
- клиент работает с абстракциями для получения нужного результата.
Пример реализации
Паттерн рассмотрим на примере чтения данных из различных хранилищ и передача их в сеть различными способами.
Создадим интерфейс IDataReader
Реализуем интерфейс в классах, которые будут иметировать чтение данных
Создадим абстрактный класс Sender
В качестве наследника создадим конкретный класс EmailSender
По аналогии создадим класс TelegramBotSender
Посмотрим использование