Pull to refresh

Comments 27

Всё таки я хочу разобраться правильно ли я понимаю «мост».

Есть абстрактный класс EntityRepository, от него наследуются UserRepository, PostRepository, CommentRepository, возможно реализующие специфичные для конкретных Entity методы (например, CommentRepository::getCommentsByAuthor).

Есть абстрактный класс AbstractEntityStorage, от него наследуются FileEntityStorage, MySqlEntityStorage, CouchDbEntityStorage, реализующие одни и те же методы сильно разными способами.

В момент инициализации (то есть в рантайме) *Repository создаётся нужный (из конфига) *EntityStorage и сохраняется в приватном/защищенном свойстве репозитория. В дальнейшем методы репозитория вызывают методы хранилища, не догадываясь какая конкретная функциональность за ними скрывается.

Это мост?
Это «мост» если:

— все методы EntityStorage подчинены потребностям EntityRepository
— EntityStorage используется только EntityRepository
— Любой Entity требует для полноценного функционирования EntityStorage
— EntityStorage ничего не знает о EntityRepository, например передача Entity в методы EntityStorage приводит к переходу от однонаправленной зависимости к двунаправленной, что здорово ограничивает возможности «независимого» развития.

В вашем случае достаточно высокоуровневая конструкция и реальным шаблоном является Хранилище(Repository) из практик DDD.
Посмотрите позиционирование GoF по мнению Ричарда Хелми в начале статьи.
С GoF шаблон Repository пересекается лишь на уровне базового принципа «инкапсуляция точек вариации» т.е в данном случае вариация в способе сохранения в Storage.
При всей простоте принципа «вариация» он фундаментален и масштабируем до уровня высокоуровневой архитектуры.
В этом единство шаблонов GoF, когда разработчик слышит «хочу менять алгоритмы» он говорит «Стратегия», слышит «хочу менять реализацию» говорит «мост», слышит «хочу менять внутренне состояние объекта» говорит «состояние», но фокус в том, что получившаяся структурная диаграмма и базовый принцип неотличимы. Различаются мотивации. Такая вот педагогический прием ориентация на «рефлекс» разработчика, «триггер» который щелкает при ключевых словах/метафорах. Даже если вся базовые принципы сведены к двум перечисленным в начале статьи.
А реализация != алгоритм?

FileEntityStorage, MySqlEntityStorage, CouchDbEntityStorage это не мост. Это адаптеры.
Вообще все пункты «Это «мост» если» выдуманы.

Это «мост» если:
— код еще не написан, либо написана заглушка
— вы сами определяется интерфейс для будущих реализаций
— можно было бы использовать наследование, но composition всегда лучше для не 'is a'.
— в мосте реализован паттерн strategy для возможность поменять реализацию

Это «адаптер» если:
— у вас уже есть старый код
— и вы хотите адаптировать его интерфейс под свои нужды
«вы сами определяется интерфейс для будущих реализаций»

об этом и идет речь когда выясняем кто-кому подчинен.

«реализация != алгоритм?»

В ООП да, под реализацией объекта понимается сочетание Данные + Алгоритмы обработки. Алгоритм по по себе не имеет состояния кроме тех данных, что он получил как входные.
В этом разница между шалонами Стратегия и Состояние, в первом мы акцентируемся на алгоритме обработки (контекст и данные передаются как входные) во втором на полном состоянии объекта.
«В ООП да, под реализацией объекта понимается сочетание Данные + Алгоритмы обработки»
Не согласен. Говорят что класс реализует интерфейс, а значит класс — это реализация интерфейса.

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

Проблема: есть пересечение множеств сущностей. В данном случае, Thread Scheduler'ов и операционных систем.
image

Добавлять поддержку новых операционных систем неудобно. Рефакторим, выделяя отдельную иерархию операционных систем:

image

Я правильно понимаю, что это не просто структурный паттерн, а паттерн рефакторинга структуры классов?
«паттерн рефакторинга структуры классов?»

Да, это прием управления эффектом мультиплексии структурных элементов.
Но не ограничивается этим и содержит ряд инвариантов (ограничений) которые показывают границы структуры компонента.
— «независимость иерархий» лишь логическое понятие, достигаемое:
а) откладыванием связывания до момента выполнения.
б) однонаправленной ассоциацией. ThreadSheduler знает о Вариантах реализации, обратная зависимость нежелательна.
в) ThreadSheduler_Implementation всей функциональностью подчинен только потребностям ThreadSheduler. Т.е явное отношение «Ведущий — ведомый».
г) ThreadSheduler не самодостаточен без ThreadSheduler_Implementation. Т.е все время пока существует ThreadSheduler ссылка на ThreadSheduler_Implementation должна быть не Null.
д) ThreadSheduler_Implementation используется только ThreadSheduler. Отношение Public use Private. Здесь можно сравнить с «жемчужиной». Все сторонние компоненты видят только «раковину» т.е ThreadSheduler, внутри которого кристаллизуется «жемчужина» ThreadSheduler_Implementation. По мере возрастания «ценности жемчужины» она впосле может отправится в самостоятельное плавание.

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

Про адаптеры:
В приведенном примере UnixPTS, WindowsPTS и.д. естественно не наследуются и никак не зависят от ThreadSheduler_Implementation. Просто имена немного неудачные, сами по себе это отдельные самодостаточные «компоненты», которые лишь используются для платформенно-зависимой реализации.
Т.е по факту у нас будет некие ThreadSheduler_UnixPTSImplementation реализующие нужды ThreadSheduler (это первично), в своей внутренней реализации использующие соответствующие платформенные примитивы. Т.е адартер вторичен и скрыт во внутренней логике.
Связь ThreadSheduler -> ThreadSheduler_UnixPTSImplementation будет «Мостом»
Связь ThreadSheduler_UnixPTSImplementation -> UnixPTS будет адаптером.
«Но здесь важно помнить, что шаблоны не определяют качество модели предметной области»

Как сказать. Например, тот же мост — будет ВСЕГДА портить качество модели предметной области, а не будет НИКОГДА улучшать. В то время как замена моста, некоторыми другими шаблонами, при большей продуманности модели предметной области будет лучше сказываться на качестве модели предметной области.
Потому что когда мы строим модель предметной области — мы не оперируем такими вещами как абстракция или реализация. И соответственно, нам не может даже в голову прийти отделить их, сделать из них две сущности. Другое дело, когда за другой сущностью (реализацией) прячется просто еще мало осознанный и не спроектированный участок. Когда вы его спроектируете окажется, что это совсем не реализация, а набор других сущностей. И я уже разбирал один пример, и там показательно, что реализацией Фигур назвали (условно) Художника (того кто умеет рисовать). Ну, это же дико, если мы строим модель предметной области — то она и близко не построена. Могу разобрать и друге примеры, подходы будут другие — но все эти примеры покажут именно это — т.к. в ООП — ну нельзя НИКОГДА разделять абстракцию от реализации, если очень хочется то это указывает лишь на то, что проектирование выполнено криво. А «Мост» как бы дезавуирует этот момент.
Приведу просто несколько цитат о проектировании.

Есть однако и правило о нарушении правил: прежде чем нарушать правило, необходимо его как следует выучить.
Робин Вильямс

В реальном мире однако, мы вначале обнаруживаем частный случай и лишь затем открываем общую абстракцию.
Бертран Мейер

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

Эрик Эванс

Удача — это основа проектирования.
Бранч Рики

Все модели неверны, но некоторые из них полезны.
Джордж Бокс

Программы на самом деле не существует. Совершенно нормально планировать работу так, будто вы строите объект недвижимости, — просто не удивляйтесь, когда кто-либо потребует его передвинуть.
Чед Лавиль
"Мы не смогли ввести собственные правила, вот и страдаем"
Речь идет о книге А. Шаллоуей, Дж. Тротт — Шаблоны проектирования.

Каким образом нотация БедныйХудожник.Нарисуй(МиллионАлыхРоз) решает проблему того, что реальное рисование выполняется c сторонних программам?..

Художник (компонент) предоставляет другому компоненту API (набор графических примитивов), для реализации которых он полагается на сторонние программы. Отношение Фигура — Кисть в исходной постановке скрыто в Художнике. Акцент идет на вариантах реализации кисти.

Итоговая формулировка А. Шаллоуей, Дж. Тротт позиционирует «мост» как прием управления мультиплексией связей.

Давайте боротся нее с формулировкой GoF (абстракция, реализация), а просто найдем «зерно», отражающее суть приема.

Обвинять мультиплексор в том, что он помешал реализовать супер-процессор убийцу Intel как то не очевидно. При проектировании думать надо о процессоре, а не логическом вентиле.

Формулировка «моста» на которой остановились А. Шаллоуей, Дж. Тротт в своей книге

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

И потом, как же не учитывать формулировку разделения абстракции и реализации? Хорошо, вот если бы мы договорились, что нужно просто выделять разные ответственности классов — это было бы другое дело. Но тогда не было бы такого абсурда как «метод DrawRectangle() находится на функциональном уровне, а DrawLine() на уровне платформенных реализаций» — это методы одной ответственности, и должны быть вместе. Искусственно их разделяя вы нарушаете модель предметной области. Ради чего? А просто потому, что «Мост».
как же не учитывать формулировку разделения абстракции и реализации?

Оставьте формулировку оригинального GoF. Вы не первый у кого она вызывает вопросы, но люди не пытаются отрицать, а пытаются понять что имелось в виду.
К тому же не забываем 3-е бандитов были практиками С++, а один ученым и практиком Smalltalk. Прочитайте про шаблон Мulticast, в течении нескольких лет 3-е видели шаблон, а программист на Smalltalk в упор не понимал о чем речь. По «мосту» эти четверо хотя бы понимали друг друга.
Даю подсказку посмотрите на структурные схемы Моста, Стратегии, Состояния. Удалите все названия классов и попробуйте найти отличия. GoF нас дрессируют объясняя очень важные вещи простым языком наметафор, «рефлексов».

Метод DrawRectangle() находится на функциональном уровне, а DrawLine() на уровне платформенных реализаций

Вы пишите «финансовый калькулятор для расчета рисков» который работает во множестве браузеров/платформ, которому лишь нужно пару фигурных стрелочек, да прямоугольников для подсветки важный чисел.
Вы серьезно думаете что клиент вам заплатит за год который вы потратите на построение универсальной графической модели?
«Мост» это не самоцель — это начальный шаг, пожалуйста развивайте модель, GoF вас к этому и подталкивает неужели не понятно он говорит «запутались в связях своей модели, давайте разорвем иерархии минимизируем зависимость и начнем все сначала».
Вот кстати, как нужно решить ваш пример с бумажными/компьютерными моделям. Эти четыре модели бумажная, компьютерная, тестовая, серийная — это некий способ представить автомобиль. Что в этом случае говорит нам ООП? А то, что автомобиль может выполнять 4 разные роли. Роли описываются интерфейсами. Поэтому создаем Iбумажная, Iкомпьютерная, Iтестовая, Iсерийная.

Эти интерфейсы все 4 — должен реализовать автомобиль. (а не легковой, грузовой автомобиль как у вас). Но эти интерфейсы автомобиль может реализовать только в общем виде, без специфики легкового, грузового транспорта. Поэтому в легковом/грузовом реализации будут уточнены/переопределены. Но мы видим, что порождать искусственную вторую иерархию в отдельных классах совершенно излишнее. Общая каркасная логика должна содержаться в автомобиле. Тем более что интерфейсы Iбумажная, Iкомпьютерная, Iтестовая, Iсерийная во многом пересекаются между собой. Кому-то может показаться, что тогда автомобиль много чего в себе содержит — но на самом деле, если будем уточнять реализация бумажной/компьютерной/… будет разделена на части по другому принципу, с выделением чертежей/постановки экспериментов/модели процессов производства.

Теперь мы видим, что Мост в данном случае выделяет искусственно иерархию в отдельный класс. В тоже время, проблема доопределения частностей для легковой/грузовой машины никуда не пропадает. И это доопределение неестественным образом происходит ни как переопределеня интерфейсов в моем решении, а как выделение новых методов в абстракции. Вспоминаем «метод DrawRectangle() находится на функциональном уровне, а DrawLine() на уровне платформенных реализаций» — вот что на самом деле происходит — DrawRectangle() — это доопределение специфики прямоугольника. Но происходит это доопределение неявно, т.е. не отражено в программе. А явно сделать нельзя, т.к. класс искусственно поделили на два.
Вот кстати, как нужно решить

Реализация автомобиля инженерами Автоваза почему то отличается от реализации автомобиля инженерами BMW.

Поймите не каждая система из 10 миллионов логических вентилей автоматически станет процессором. Но без знания базовых элементов вы тем более не сможете спроектировать процессор.

Если у вас есть уникальные идеи по архитектуре ПО, то оформите их по аналогии в описанием паттернов. Назначение, Имя, Техника, Примеры (можно Open Source).
Это будет более конструктивно.
На мой взгляд все уникальные идеи и «логические вентили» сформулированы у Г. Буча в его «Объектно-ориентированный анализ и проектирование». Этим я и пользуюсь. Это достаточно более чем. Ну может быть еще про UML почитать полезно. А все эти паттерны, DDD — это не более чем хорошо забытое классическое ООП — так сказать болезненный переход специалистов от структурного подхода к объектно-ориентированному. Просто даже интересно читать — с каким пиететом пишут книги о DDD («перевернуло мышление», «теперь не так отношусь к классическому ООП — над которым раньше потешался»). А ведь просто нужно было ООП изучить.

Я же не призываю использовать что то новое — все о чем я говорю — это элементарно давно изложено.

Вся теория паттернов — это излишество — болезнь от ума. Да есть полезные паттерны, но есть и не полезные. Но даже если оставить полезные — то они легко получаются с комбинации наследования/агрегации и еще ряда приемов классического ООП, которое Г. Буч более чем самодостаточно описал.
«Серебрянной пули» нет.

Чтобы не возникало ощущения противоречия между великолепной и фундаментальной работой Г. Буча и паттернами «Банды четырех» добавлю, что паттерны рекомендованы Бучем к ознакомлению после главы 4. Которая посвящена принципам классификации/категоризации.

Большая работа была проведена в области каталогизации программного обеспечения, в частности, в области классификации идиом, механизмов и принципов. С этой точки зрения большой интерес представляют работы Гамма и др. (Gamma et al.) [E 1995],
Г. Буч
Противоречия в «большом» то нет, но вот излишество есть. Ну не вижу я там чего-то принципиально нового, так некая сборная солянка, после которой возникает ощущения безвкусия. Но это да — видимо на любителя. Но если уж «верить» паттернам — то нужно быть ДО ЭТОГО вооруженным классикой ООП. Тогда по крайней мере можно отличить некачественное и в 99,9% ненужное применение шаблона «Мост».
Вот, кстати, паттерн «Мост» должно иметь четкую ассоциацию с денормализацией. Понятно, что на денормализацию иногда идут, вот с Мостом — та же история.
Спасибо! Пожалуй, самая полезная статья в серии «мостов».
Sign up to leave a comment.

Articles