Программирование / [Из песочницы] Паттерн Bridge, как он есть

в 13:19, , рубрики: bridge, design patterns, паттерны проектирования, метки: , ,

Прочитав статью товарища tac, посвященную злополучному паттерну проектирования Bridge (от англ. — «Мост»), мне стало очень обидно и за Банду Четырех, чьи идеи были самым бессовестным образом осрамлены, и за сам паттерн, который был ужаснейшим образом дискредитирован в глазах менее опытных читателей. Не в силах больше смотреть на пылкие дебаты, разгорающиеся в комментариях, я решил спасти репутацию бедного паттерна, виновного лишь в том, что вот уже в который раз, он был неправильно истолкован.

Если вам еще не надоели статьи о паттернах проектирования или если вы желаете лучше узнать, что же такое на самом деле этот «мост» — милости прошу под кат:

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

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

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

Проблема

Иногда встречается необходимость реализовать некоторую абстракцию разными способами. К примеру мы разрабатываем на заказ кроссплатформенную игру для Window и MacOS. Причем заказчик хочет, чтобы движок для отображения трехмерная графики в версии для Windows был реализована на Direct3D API, а в версии для MacOS — на OpenGL.

Возможные пути решения

Подход «дедовский» — прямой

Прямой метод решения такой задачи — разрабатывать две отдельных версии движка с общим интерфейсом, но различной реализацией. В этом случае «на выходе» мы получим две различных версии движка — одну исключительно для Windows, вторую — для MacOS. Такой подход уместен, однако в дальнейшем придется прикладывать очень много сил для поддержания обеих версии, отслеживания внесения изменений в общую структуру каждого движка, чтобы обеспечить их «симметричность». Кроме того, это вызовет дополнительные сложности и для заказчика, так как ему, фактически, придется тиражировать игру под каждую платформу индивидуально, что приводит к дополнительным сложностям и издержкам.

Объектно-ориентированный подход

Более разумный — объектно-ориентированный подход, куда удобнее в таком случае. Было бы достаточно объявить абстрактный класс графического движка, определяющий его интерфейс, а непосредственную реализацию отображения трехмерной графики при помощи различных API можно вынести в подклассы движка. Это позволит объединить версии игры для различных платформ в одну, снизив затраты на разработку и поддержание отдельной версии. При этом для отрисовки трехмерной графики игра (реализованная в отдельном модуле) будет обращаться к графическому движку через указатель на конкретный подкласс с его реализацией под нужную платформу. Получается, что графический движок отделяет собственную абстракцию от конкретных реализаций на нескольких графических API, создавая своего рода «мост» между различными частями программы и графическими библиотеками.

Собственно, именно такой способ проектирования и является паттерном Bridge.

Встречайте: Bridge

Рассмотрим определение назначения паттерна проектирования Bridge из знаменитой книги «Банды Четырех» — "Приемы объектно-ориентированного проектирования. Паттерны проектирования."

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

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

Причины неверного толкования паттерна Bridge

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

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

Более того, даже если вам необходимо создать приложение, в котором в каждый автомобиль можно установить другой двигатель, в Bridg'е все равно не будет никакой необходимости. Достаточно будет объявить абстрактный класс Engine, от которого можно будет образовать подклассы для различных моделей двигателей. Почему так? Все очень просто. Сложности в толковании паттерна Bridge возникают из-за того, что его зачастую пытаются применить к классам, моделирующим объекты реального мира, что в корне не верно.

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

Надеюсь, что эта статья наконец-то расставит все точки над i в теоретическом определении паттерна проектирования Bridge и прекратит бессмысленные споры на этот счет.

Автор: Ataraxer

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js