Предистория
Я прочитал эту статью о паттерне проектирования «Мост». Увы, его очень часто используют не верно. Более того, я затем открыл книгу Приемы объектно-ориентированного проектирования. Паттерны проектирования. Оказалось и там авторы очень смутно декларируют причины его наличия и когда его использовать. Поэтому ниже я вам сообщу как и зачем подобное использовать.Что такое паттерн проектирования «Мост» на самом деле
Если знать объектно-ориентированное программирование, то со всей ответственностью заявляю, что знать о паттернах совершенно не обязательно. Паттерны это лишь частное, и не всегда самое удачное, решение на базе ООП принципов.
Посмотрим, что такое паттерн «Мост», что кроется за этим заумным термином. Это не что иное как комбинация применения наследования и агрегации. Увы, часто не знают, что такое агрегация — по простому, это когда один объект включается в другой.Истинные причины использования паттерна «Мост»
Авторы книги декларируют такую цель: «отделить абстракцию от её реализации».
Но это совершенно не верно. Как принцип вообще — это даже вредно. Это противоречит ООП. Реализация должна полностью соответствовать тому, что декларируется в абстракции. Иначе, грош цена вообще отделению декларации от реализации. Если мы смотрим на декларацию класса, а потом оказывается, что реализация класса ей не соответствует значит мы потеряли самое главное — мы потеряли полиморфизм. Мы тогда становимся вынуждены знакомится с реализацией, и нам не достаточно знать только абстракцию/спецификацию.
Но и дело то оказывается в несколько другом. И это становится ясно на примерах. Показывается примеры, где применяется наследование. Но наследование не простое, а по двум разным критериям. Например, «Окна» могут классифицироваться как для разных операционных систем: XWindow, PMWindow, ..., так и разные по назначению: окна для пиктограмм (IconWindow), окна для сообщений (MessageWindow)…
Тогда конечное окно должно содержать функции из одной иерархии и функции из другой. Т.е. быть специализированным по назначению и для операционной системы. Тогда, конечно аппарат наследования становится проблематичным, получается надо создать классы как перемножение этих иерархий, т.е. XIconWindow, PMIconWindow, XMessageWindow, PMMessageWindow. Конечно, это уже нарушает другой принцип ООП — создание лишних сущностей, которые есть всего лишь комбинация более простых.
Отметим, что это классический пример применения множественного наследования. Т.е. XIconWindow есть наследник от IconWindow и XWindow. Но новейшие веяния определили множественное наследование как несоответствущие ООП. Здесь обсуждать не будем, но действительно теоретически множественное наследование излишнее, и есть скорее непонимание предметной области.
Так вот авторы паттерна, предлагают другой путь. Они увидели частность, что одна из иерархий отвечает за физическую реализацию, т.е. для какой операционки это делается. И конечно это должно быть скрыто. Но это лишь частность, а на самом деле могут быть другие иерархии. Например, окна могут различаться еще по принципу Однооконное (SDI) или Многооконное (MDI). И что еще одно перемножение иерархий?
И конечно, нет. Спасает, тут понимание того, что эти иерархии объектов — это их свойства. А свойства как нужно реализовывать? Правильно, включая их в определяемый объект. Т.е. применять агрегацию.
Вот это и делает паттерном «Мост». Но по сути этот паттерн — это неуклюжие объяснение более обих принципов ООП.А как же тогда рассуждать более правильно?
Очень просто. Есть отношения общее-частное — применяем наследование, есть отношения часть-целое применяем агрегацию. Но иногда это не столь очевидно, и нам кажется, что есть отношения общее-частное но по двум, трем различным критериям (как описано выше). Но мы помним, что такое рассуждение нам позволит только запутаться, и соответственно, превратить ухудшить архитектуру программы. Тогда мы должны решить какие из критериев более важны, а какие менее. Выстроить их в иерархию, вложить одно в другое. Авторы паттерна «Мост», считают что реализация на какой операционной системе менее важна, чем назначение окна. Но это лишь частный случай. Могут быть и другие критерии, и каждый раз придется решать что есть часть, а что целое.
Более того, частенько это придется решать когда уже часть реализована, и появилась дополнительная постановка. Тогда придется заниматься рефакторингом и наследование превращать в агрегацию. Поэтому нужно помнить, что наследование создает между классами более жесткую связку. И всегда, когда есть возможность лучше предпочесть агрегацию. Это позволит более четко сформировать спецификацию класса и отношения между разными классами. Наследование же надо использовать очень аккуратно. Иначе потом придется его переделывать в агрегацию — а это часто не так просто. Наследование нужно использовать только тогда когда вы четко не можете выделить критерий наследственной классификации, т.е. когда вы знаете, что этот класс есть подвид более абстрактного класса, и не по какому то критерию, а вообще.
Но никогда не выделяйте реализацию «будильника» от абстракции «будильника». Это говорит лишь о непонимании ООП. Так в статье, которая послужила причиной для написания этой заметки в качестве «моста» вынесена сущность, объявленная реализацией будильника. Интересно, а то, что осталось в т.н. абстракции будильника — не реализация?
На самом деле там вынесены две функции — зазвонить и выдать сообщение. Причем это можно сделать разными способами. Очевидно, что объединение этих функций искусственное, т.к. возможны разные их комбинации. Поэтому абстракция будильник у нас состоит/использует не псевдореализацию, а две сущности — абстракцию менеджера сообщений и менеджера проигрывателя мелодий. А уже конкретные классы с помощью конструкторов задают какой тип сообщений и как проигрывать мелодии нужно.
Вывод один: паттерны проектирования, особенно плохо продуманные такие как обсуждавшийся «Мост», и особенно теми кто не понимает более важные принципы — лишь мешают, путают программистов, и их программы.