Pull to refresh

Comments 231

Я, честно говоря, не понял, каким образом отделение абстракции от реализации вступает в противоречие с тем, что реализация должна соответствовать абстракции.
Вопрос в методе. Давайте вначале определимся, что есть абстракция, а что реализация. Начнем издалека. Если мы классически подойдем, то что такое абстракция? Это некая спецификация класса, т.е. в С++ — это .h файлы. А реализация это собственно тело методов, т.е. в С++ — это .cpp файлы. В С# уже отошли от этого, и так рельефно спецификацию от реализации уже не различают. Почему? Потому что фактически это не удобно для редактирования. Поэтому разделение на абстракцию/реализацию уже мыслится более условно, как спецификация (количество и тип входных/выходных параметров) методов/свойств и прочего и их реальная реализация в коде.

Теперь к нашим баранам. Ну посмотрите на статью, про будильники. Объясните мне пожалуйста, почему LockupAlarmClock — называется абстракцией, а AlarmClockImpl — реализацией?

Это настоящая фикция, и тот и другой класс имеет и абстракцию и реализацию. Тогда важный вопрос: чем реализация LockupAlarmClock отличается от AlarmClockImpl? Да, ничем — совершенное произвольное разделение одной сущности будильник на две с неясными границами.

Зачем? Хотите скрыть от использующего детали реализации — объявите видимость private. Зачем порождение излишних сущностей? И как оказывается совсем не для этого — об этом статья и написана.

А теперь отвечая на вопрос — по сути такое использование — это вынесение всего private в отдельный класс. Зачем? Если как я показываю в статье есть причины из-за разных классификаций при наследовании, это одно (т.е. выделение в подкласс методов ответственных за под задачи — рефакторинг «Выделение подкласса»). Если же искусственно как у будильников — это только ведет к непониманию предметной задачи, точнее ее точек роста.
Стоп-стоп. Вы про методы ничего не говорили. Может быть, просто вы неправильно методы выбрали, и не стоит клеймить определение?
Давайте по очереди. На примере будильников Вы согласны? Тогда пойдем дальше. Я утверждаю, что выделить реализацию из класса (точнее деля их на два) нельзя в принципе. Это будет не реализация, а одно из двух или произвольное разделение на сущности, или то о чем статья выделение агрегирующего класса, но тогда это будет не реализация «будильника», а просто другой класс, который нужно агрегировать в «будильник». Поэтому я говорю вообще. Я рассмотрел про будильники — как неудачное выделение методов. Но я также рассмотрел и изначальный пример авторов из книги — и он тоже не удачный в принципе. Ниже в комментариях мне предложили еще один пример — посмотрю напишу. Если хотите предложите еще вариант — любой пример я смогу объяснить чем плох, или что он просто не о том (просто совпадение применения агрегации, с такой ненужностью как «мост»).
Я не согласен с некоторыми положениями из вашего предыдущего комментария.

Остановимся на них поподробнее.

«Если мы классически подойдем, то что такое абстракция? Это некая спецификация класса, т.е. в С++ — это .h файлы.» Это неверно. Файлы и прочая ерунда не имеют к абстракции никакого отношения. Просто в силу того, что в .h файлах есть секция private.

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

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

Кажется, что такая декомпозиция избыточна. Однако, бывают случаи, когда она необходима — например в том случае, когда одни и те же идеологические элементы реализуются с помощью разной логики. Классический пример — окна в Windows и Linux. Под собой они имеют принципиально разное API, которое, однако, может легко сводиться к некому intermediate контракту. В этом случае общая абстракция «окно» разделяется на отдельные реализации «windows-окно» и «linux-окно», для которых может иметься возможность выделения общего интерфейса на основе уже тех данных, которые мы получили при декомпозиции — в этом случае мы получаем классический pimpl.

Сама по себе декомпозиция такого уровня при отсутствии сходных абстракций с разными реализациями, конечно, бессмысленна.
Ну вот видите. Уже хорошо. Мы видимо по разному понимаем понятия абстракция/реализация. Абстракция != Публичный контракт/Публичные члены класса. Абстракция класса — это его полная спецификация, т.е. именно то, что в .h файле. Если ты не только используешь класс, а развиваешь его, то важно понимать всю абстракцию, т.е. и приват члены. Но это не означает, что ты опускаешься до знания реализации. Это все равно, что видеть UML диаграммы и не видеть код. Вот что я и Гради Буч понимает под абстракцией. Реализация это и реализация публичных и приватных членов — т.е. вся.

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

Вот содержимое моего .h файла.

class Pillow
{
public:
Pillow();
~Pillow();
int Pie(int vine, double meat, const char* cucumber);
}

Какую абстракцию он описывает?
Это абстракция некоторой сущности Pillow, которая только умеет делать Pie. Все остальное без комментариев не переводимо.
Ну вы же говорите, что абстракция — это содержимое .h файла.

Может быть, вам необходимо что-то еще, чтобы понять, что именно делает класс, а простого содержимого файла недостаточно?
.h файл это минимум. Еще не плохо иметь диаграммы UML. Вы жу говорите, что вам достаточно паблик части, т.е. еще меньше. Чисто технически мне будет достаточно .h файла в статическом смысле, т.е. как диаграмма классов. Но не зная приват части, я буду представлять абстракцию еще меньше. Но при чем тут это?
Я не говорю, что мне достаточно програмных описаний.

Вы знакомы с понятием «публичный контракт»? Что туда входит?
Ну, объясните мне — что Вы под этим понимаете? Удивительное рядом :)
Публичный контракт — это модель внешнего поведения объекта, которую он обязуется соблюдать. Более ничего.

Никаких .h файлов там и рядом не лежало.

Одного описания функции мне мало для того, чтобы понимать, что если я передам в функцию Pie значения 1, 2.7 и «lower» она вернет мне 8.

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

Так, публичным контрактом для функции strToLower является описание: «функция [...] возвращает исходную строку, в которой все символы переведены в нижний регистр». А реализация — это уже вопрос десятый, и будет она это делать по таблице замен, либо через функциональное соответствие — пусть программисты решают.
Ну, не правы. Что я могу поделать :) Спецификация класса или ясна или нужно делать комментарии. Но наличие/необходимость комментариев не означает, что из спецификации нельзя понять что обязуется делать функция или класс.

Хотите спорить дальше — приводите источники, где определен термин «публичный контракт», заодно выучите
Окей, вы во всем абсолютно правы. Вы победили.

Надеюсь, это сделает вашу жизнь несколько лучше.
Я бы победил, если бы заронил в вас хоть каплю сомнения. А так увы…
Пусть эта мелочь не омрачает вам сладкий миг торжества.
«Контролируйте доступ. Модификатор public должны иметь только те члены класса, которые составляют публичный контракт этого класса и которые действительно используются другими классами. Все остальные члены должны быть скрыты для внешнего доступа (инкапсулированы). Следите за публичным контрактом своих классов и не допускайте появления в них ничего лишнего.»

stump-workshop.blogspot.com/2008/09/blog-post.html

UFO just landed and posted this here
Я вообще не понял о чем сей пост, и зачем автор решил, что знает зачем паттерн и как его применять лучше, чем создатели паттерна. Не понимание ООП, понимание ООП, частное общее, общее частного, частное от общего, частное от частного, что к чему, черт ногу сломает, хотя бы диаграмм вставили…
Да и вообще зачем давать совет если сами, судя по всему, не понимаете зачем этот паттерн? Используйте чистое ООП и НПИ и не тратьте свое время на как вы сами сказали не обязательное!

И пример с окнами там абсолютно логичен. И как написали выше, отделение абстракции от реализации не чем не противоречит ООП, а лишь грубо говоря является выделением общего интерфейса, для нескольких реализаций класса. И никого не должно волновать, как работает черный ящик.
Проблема в том, что «Мост» — это анти-паттерн. И вот вам такой вопрос: как по вашему использование паттернов — это чистое ООП или нет? И еще: когда вы разделяете один класс на два — вы это делаете произвольно или по какому-то критерию? Если по критерию то, как вам кажется должны ли появится значимые сущности или допустимы искусственные образования? А если мы не можете дать четкое название классу и пишите «НазваниеКлассаImpl» — вы понимаете за что ответственен класс?
У вас настолько шокирующие откровения, что я бы оценил ваш опыт работы программистом в 1 год. Скорее даже меньше. Это так?
Зря вы так. Если хотите знать — я работаю с солидной фирме ведущим программистом и проектировщиком. Уже более 10 лет. Я то, что я пытаюсь объяснить, что некоторые серьезно заблуждаются — увы это лишь опыт, и желание предостеречь остальных… Но мы тут не для того, чтобы мерятся… Поэтому давайте или конструктивно с аргументами или никак…
Я не понимаю, как человек с десятилетним опытом может не понимать столь очевидных вещей.

Это выбивается куда-то на задворки моего подсознания вместе с Гендальфом с мечом в левой руке.
«Я автор текстов и статей»©
Это достаточно распространенный диагноз. Человек не читает книг, не развивается при этом думает что он шибко крутой специалист — ну а че «солидная транснациональная корпорация — ведущий архитектор-тим-лид-суперстар» зачем ему эти GoF и кто такой этот Фаулер что с ним все носятся.
«У меня нет времени — через два дня релиз»© и так всю жизнь…
Спорить с такими персонажами забавно, но бесполезно
Ну что ж. Чем больше таких специалистов — тем более ценен я сам.
Паттерн — шаблон решения типовой ситуации, чаще всего лучшая практика его решения, ООП — это инструмент, который может применяться для его решения, вы не поверите, но тот же singleton реализуется вообще без ООП.
Как вы будете разделять свой класс это ваши проблемы, главное что бы выделенные сущности были достаточно общими для вашей проблемы.
Нейминг я вообще не понимаю как относится к тому о чем мы сейчас говорим.
habrahabr.ru/blogs/complete_code/138357/#comment_4618644 вот тут я описал примерно зачем и как.
В том то и дело, что паттерн «Мост» — это плохая практика решения типовой ситуации. Тут опережая вас хочу спросить: а какую типовую ситуацию решает «Мост»? Опишите.

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

Остальное почитаю. напишу.
UFO just landed and posted this here
Раз пошла такая пьянка, потрудитесь объяснить почему мост является анти-паттерном!
Приближение №1. (далее по мере квалифицированных вопросов)

Просто потому, что реализация прячется с помощью единственного модификатора private называется.

А другой необходимости в паттерне «Мост» еще не декларировалось. Расскажите, что он решает — обсудим.
С каких пор сокрытие реализации сразу превращает паттерн в анти-паттерн?
А я такое написал? Я написал, что для сокрытия реализации необходимо и достаточно воспользоваться модификатором private, и ради этого не нужно ничего более и это излишне и вредно.
Вы очень сильно заблуждаетесь. Покажите как вы тогда сможете изменить приватную реализацию метода не изменив при этом сам класс?
Давайте вы не будите делать субъективных оценок.

Представьте мне ситуацию/задачу конкретно — и я вам покажу что угодно :)
конкретный пример:
Имеется окно. Цвет и размер окна хранятся в файле (пусть будет XML, это не важно)

Исходя из ваших слов, мне достаточно в классе окна получать цвет и размер при помощи приватного метода. Что то типа
private function getParam(){
return readXML();
}


Но что будет если конфигурация приложения изменилась и теперь параметры хранятся в БД? Вам ничего не останется как изменить уже существующий класс, вставив вместо readXML() что то типа select * from table window.

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

1. select * from table window — мелочь, но важно к базе нельзя обращаться прямо, только через хранимые-процедуры, а за вызов хранимой процедуры ответственен определенный класс поэтому будет что-то типа. Database.Run(«GetColor»); Database.Run(«GetSize»)

2. readXML(); — тоже будет другой. Вида XML.Read(«Color»); XML.Read(«Size»);

3. Если конфигурация изменилась делаем просто

private function getColor(){

//return XML.Read(«Color»);
return Database.Run(«GetColor»);

}

Другое дело, если может быть и так и так. Но как передано в конструктор. И далее достаточно написать элементарный if от значения переданного в конструктор.

Вы описали, более сложный случай — он называется обобщение. Для данного случая излишнее, совершенно незачем сводить спецификации работы с XML и Базой данной к одному стандарту — есть существенные различия с работой, и незачем их сводить к одному типу. Но есть другие варианты, например, таким образом я сводил работу с DataTable при работе с базой данных, но они были от разных производителей.

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

UFO just landed and posted this here
Если не отделять SQL от кода, то зачем вообще что-то отделять? А если не отделять основы, т.е. работу с базой, где совершенно другая логика чем в ООП — то ни чего худшего представить нельзя.

Если же говорить о закомментировании уже не нужного кода — это отдельная тема, но это лучший вариант, чем рожать то, что никогда затем не будет использоваться, и будет лишь перегружать логику кода.
UFO just landed and posted this here
1. Вызов хранимых процедур нужно организовать через специально заточенный класс (я писал об этом). Для маленьких проектов вообще можно не думать о совершенстве кода, речь не о том. Самое серьезное возражение, это наличие в SQL бизнес-логики. Это правда. И это тема отдельного разговора. Но с одной стороны хранимые процедуры могут быть тупыми «сохранять/доставать» в/из базы. С другой стороны, часто клиенты стремятся к максимальной автоматизации. А это означает, создание записей в базах данных БЕЗ интерфейса, пакетная автоматическая обработка. Тогда насиловать ООП языки программирования — это не серьезно. Это задача для SQL-процедур. Они работах быстро и хорошо на большими объемами реляционных данных. И там, конечно, есть бизнес-логика, но она не связана с поведением клиента/интерфейсом. Т.е. да там (в SQL-процедурах) мы вынуждены дублировать некую бизнес-логику, но со своими особеностями для пакетной обработки. Та же бизнес-логика при работе с клиентом, через интерфейс пользуется более «тупыми» SQL-процедурами, или процедурами более внутренней кухни баз данных (триггерами на обновление и прочие). Поэтому тут как раз таки и есть более четкое разделение на слои. Бизнес логика работы с клиентом через GUI, и пакетный прием данных и обработка минуя интерфейс. Эмулировать пакетную обработку, через ОО интерфейс — это извращение.

А что касается, NoSQL-продуктов, ИМХО, это чистый бред — никто еще не создал что-то более качественное при работе с большими объемами данных, чем реляционное представление. Многие баловались объектными базами — но это не нашло серьезного развития, а отрицание NoSQL — это просто не серьезно.

Про комментарии — похоже вы не поняли мой изначальный пост, и возражаете не мне.
UFO just landed and posted this here
1. Я ни кого не учу, а выражаю свое мнение.
2. Нет указанные фразы не противоречат, как минимум в рамках разумного. Так как там разная бизнес-логика, точнее в разном контексте. И не думаю что вы предложите лучший компромисс
3. Клиент ставит задачи, я предлагаю соответствующую архитектуру. Тут вы зря иронизируете.
4. Реляционное представление — старье?
5. Разделению на слои, и ответственности — моя обязанность учить, и именно работа через хранимые процедуры — это разделение. А вот подмешание sql выражений в ООП код — это бардак, а не разделение
6. Может соизволите указать, что такого революционного предложили продукты NoSQL — что это заслуживает внимания?
UFO just landed and posted this here
4. «Живее всех живых»
5. Хоть одну достойную реализацию покажите? (я же начал с того, что в ОО базах много званых, но не одной избранной)
6. Тесты сравнения с Ораклом, или не было
UFO just landed and posted this here
От ORM в .NET мы отказались вполне осознано.
Сыро все очень, думаю в других языках состояние ее хуже…
6. Впрочем MongoDB можно попробовать, пишут вроде нечего в пару раз производительность типа лучше. Но это конечно баловство, слишком молодой продукт… А в принципе спасибо, за наводку «не съем — так покусаю»
Укажите что почитать про Redis, пока впечатление, что очередной молодой проект, который заглохнет, через пару лет. Мне нужна литература, где явно на примерах показывается чем это лучше реляционных баз данных. И нарезки типа — есть еще вот такая фича — малоинтересны.
Почитал. ИМХО, Оказалось ее хуже чем думал. Винды не поддерживает, не стабильная версия, с многими вещами незаконченно/проблемы — вообще «мои» солидные клиенты, такую хрень никогда не поставят :) Несерьезно даже обсуждать. Все более убеждаюсь, что в этой сфере баз данных, нечего даже рассматривать то, что разрабатывается компаниями меньше уровня Oracle или MS — трата времени. Даже не знаю кто и главное зачем тратит время на что-то другое :(
UFO just landed and posted this here
Да, читаю… много барахла, фильтровать не успеваю :(
А что касается вашего исследования, то пока лишь вы и ряд читателей нападают на меня личностно… я же не позволяю себе судить о том, что кто знает и какие у него знания… и почему некоторые думают, что не использую — значит не знаю? Чаще не используют по вполне убедительным причинам, или их отсутствию для использования ширпотреба
Если считаете, что есть лучший компромисс — напишите какой.
«я писал об изменении кода в исходном классе, а не про комментарий»

И что Вы писали?

"//return" — я вам объяснил зачем комментирование? Вы хотите вместо этого написать 50-строк кода, с введением кучи излишних сущностей?

И думаете не измените код в исходном классе? ИЗМЕНИТЕ! Хотя бы для того чтобы строку return readXML(); заменить на вызов выделенного вами более общего метода, через интерфейс или абстрактный класс.
Короче, все ваши NoSQL-продукты — это попытка реанимировать отжившие свой век сетевые базы. Со всем вытекающим.

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

Но наличие NoSQL-продуктов не позволяет раскидывать обращения к базе в коду в перемешку к бизнес-логикой.

Вот об этом я и писал, как основе. Что касается баз. А пост мой почему-то заминусовали. Не ясно почему.
UFO just landed and posted this here
Хорошо, почитал. Только не очень понял, что по вашему я должен там был увидеть. Вначале говорится о том, что это не технология, а движение. Ну, ладно ООП тоже вначале рождалась теоретически. Видимо и сейчас такой этап у NoSQL. Но пока не появилось объектных языков, от теории ООП было мало толку. Тоже самое и с NoSQL сегодня. Наверное, да можно чем то пожертвовать, но автор статьи не пишет чем и главное ради чего. И еще я не очень его понял про пакетную обработку, что он предлагаем вместо этого?
UFO just landed and posted this here
Ах вот оно что, тогда конечно пойду изучать :)
Да, и еще большой вопрос: а почему это окно должно заниматься чтением каких-то своих свойств от куда то. Это как правило задача более верхнего класса. Поэтому в окне будут лишь свойства Цвет, Размер… а скажем, МенеджерОкон, будет обращаться куда надо и присваивать свойства перед открытием окон. Так будет правильнее, и заметьте ни каких даже интерфейсов (ни говоря уже о Мосте), и класс окна не меняется. Будет меняться МенеджерОкон — но тогда см. выше, то решение размещается в МенеджереОкон. А там уже в зависимости от необходимости, если уж сильно хочется — можно и интерфейсы навесить — но лучше не надо — пользы мало.
Я бы сказал что вы упорны, но это не так. Вы упрямы. И вы упрямо не хотите прочесть информацию о данном паттерне еще раз.

То что окно не должно читать свойства — не обращайте внимания, это просто пример.

Теперь объясните как же получается что вы смогли изменить приватные методы класса не изменив сам класс?
Я уже специально для вас сделал дополнение — «Мост» это не агрегация абстрактных классов и не использование интерфейсов. Хотя именно это один из способов не изменять сам класс. Но это требование не обязательно вообще. Вам кажется это ключевым для «Моста». Хорошо. Но там имеется введу другое — после выделения реализации её можно изменять как хочешь. Просто включение абстрактных файлов или использование интерфейсов — это техника, использующаяся в «Мосте» с определенной целью: избавится от множественного наследования. Вы обсуждаете применяемые для этого техники — все равно, что обсуждать хорошо ли наследование вообще, а не то для чего его использовать.
Вам уже где то здесь в комментариях написали. Вы придумали какой то свой мост. Банда четырех про такой мост ничего не знает.
Ну, почитайте хотя бы их примеры… может поймете что «примус» там в другом.
Ну, и формулируйте точнее: изменить приватную реализацию метода, не изменив публичной спецификации класса. Так? В общем случае, именно для это приват модификатор и нужен, поэтому без конкретики это лишь подтверждает мои слова.
НЕТ! Речь не про публичную спецификацию класса! Речь про весь класс целиком! Изменить реализацию нужно так, что бы файл с абстракцией даже не нужно было открывать (представьте что он закодирован и исходников у вас нет).
Какая разница? Ну, пусть C#
А зачем? Это нужно только в определенных системных слоях (каркасе). Если это будет всюду — будет т.н. перепроектирование. Но это к «Мосту» отношения не имеет выше написал на вашем примере.

Это вы просто используете по назначению концепцию «интерфейса». «Мост» причем? Да, не причем… совсем о другом «в огороде бузина, а в Киеве дядька»
Мост как раз при чем. Читайте определение паттерна. Используя его во всех указанных примерах мы можем выбрать конкретную реализацию во время выполнения программы.
Чтобы выбрать реализацию, совсем не нужен «Мост», достаточно в конструктор передать класс реализующий интерфейс, и пусть он его использует. Это же не мост.
Уже достаточно примеров приведено, Вам мало?
Если конкретно, в комментарии ниже я привел ссылку. Там прекрасно показано зачем нужен мост.
Не показано, там мост не применяется — там применяется техника работы с интерфейсами и все.
«И никого не должно волновать, как работает черный ящик.» — я уже писал выше — объявите private, зачем классы городить тогда? И пример с окнами логичен только в том смысле как написано у меня в статье, как разделение двух иерархий при наследовании — и ни для чего больше. Но это не имеет отношения к разделению абстракции и реализации. И как и ожидалось такая формулировка лишь путает… поэтому позвольте уж мне высказать, чем авторы паттерна совершенно не правы.
Есть такой термин — повторное использование. Если у вас всего одна единственная сущность с одной единственной реализацией — будильник, то да вам этот паттерн не нужен.
Ну, напишите когда он нужен. Пока с этим не автор «будильников», не авторы паттерна не справились. Не бойтесь использовать термины, я их знаю :) Поясните как разделение класса на два с неясной целью помогает повторному использованию?

Во в статье я пишу, что разделять конечно нужно — это агрегация. Но разделять нужно не механически якобы абстракцию от якобы реализации. На поверку то оказалось, что выделять нужно не якобы реализацию будильника, а просто использовать менеджер сообщений и менеджер проигрывателя мелодий. А если их еще нет — вот что нужно выделять.
Я ниже привел пример про машины. Можете представить, что у вас 5000 машин и 1000 движков, а ваша цель — создания стенда подбора движка к машине.
не поясните, что такое НПИ?
Статья про будильник совершенно непонятна. Ваша статья ничуть не лучше, пример из википедии не очень удачный.
Стоило бы написать так же что мост очень похож на стратегию.
Мне кажется здесь приведён очень удачный пример.

Мост весьма удобная штука в некоторых ситуациях, НО только если он не очень длинный. Я встречал реализацию с 5 вложенными уровнями классов. Это было просто
image
И чего минусуем? Поясните где я не прав.
Я минусанул за огромную картинку
Пример совсем о другом. К «Мосту» отношения не имеет. Где же тут выделена реализация «Письма»? Выделено вполне законно просто «Транспортировщик Писем»… «Мост» курит в сторонке.
Ну и чтобы было понятнее зачем паттерн — он позволяет гибко комбинировать и заменять части классов при этом реализации этих частей имеют общий интерфейс он же мост.
К примеру есть куча машин, но у каждой есть двигатель, а двигателей тоже как известно много, но все делают одно и тоже — создают, грубо говоря, вращательный момент (возможно очень не точно выразил термин, но вы поняли), поэтому мы можем в класс каждой машины, агрегировать интерфейс двигателя с единственным методом — получить момент, и внутри класса работать именно с этим интерфейсом, а как он реализован заботить никого не должно. А затем при создании экземпляра машины подсовывать нужный двигатель. Вот и все!
Все по моему очень просто. и ничего не противоречит ООП
А так как двигатели используют разное топливо, то при создании двигателя для машины, подсовывать ему нужное топливо.
Пример с машиной отличный. Всю статью можно заменить этим комментарием.
Так кто с этим спорит? Только «Мост» тут причем? Это классическая агрегация. А еще у машины есть колеса и фары… и что? А ведь «Мост», говорит нам о чем? По определению — указывает, что колеса, фары и двигатели нужно объединить в одном классе — «МашинаРеализация»… вот это и есть нарушение ООП. Статью внимательно читали?
Кстати, интересный момент.
The bridge pattern is a design pattern used in software engineering which is meant to «decouple an abstraction from its implementation so that the two can vary independently». The bridge uses encapsulation, aggregation, and can use inheritance to separate responsibilities into different classes.

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

Может быть, вы не туда посмотрели с вашим определением?
И что это меняет?
Ключевое «разделять абстракцию и реализацию» — никуда не пропало.
Ключевое здесь — это цель разделения. А цель обозначена явно — «чтобы они могли изменяться независимо».

Без цели действительно разделять их бессмысленно.
Где в вашей статье черт подери дано хоть какое то определение? Ваша статья необработанный поток сознания.

Возьмите определение хотя бы из википедии:
Bridge, Мост — шаблон проектирования, используемый в проектировании программного обеспечения чтобы «разделять абстракцию и реализацию так, чтобы они могли изменяться независимо». Шаблон bridge (от англ. — мост) использует инкапсуляцию, агрегирование и может использовать наследование для того, чтобы разделить ответственность между классами.

Кто вам сказал что нельзя отделить абстракцию от реализации заменив одно из свойств объекта другим объектом, который реализует необходимый интерфейс?
Поменяем объект двигатель на объект другого типа с тем же интерфейсом (другой двигатель), и машина все равно поедет без каких либо вмешательств. Вот вам абстракция машина. Она не поменялась при смене реализации двигателя!
Все определения известны и я указал откуда они. От авторов. Зачем мне третичные определения, даже если они частично совпадают.

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

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

Еще раз повторю, когда вы из машины выделили двигатель — это замечательно. Но «Мост» говорит выдели реализацию машины в отдельный класс, т.е. выдели работу фар, колес и двигателей в один файл и назови это реализация машины. По вашему не так? А именно это и есть выделение реализации. Когда же вы выделяете отдельно три класса из машины, и понимаете, что выделяете абстракции фар, колес и двигателей отдельно — вы занимаетесь ООП декомпозицией, что только можно приветствовать. Но «Мост» тут совсем не причем.
В описании паттерна я нигде не нашел фраз «выдели в отдельный класс».

Вы придумали какую-то хрень вместо «моста» и теперь героически ее побеждаете.
Точно точно. он не ищет проблем, он создает их сам :)
WindowsImp — если это не отдельный класс, тогда нотация UML это просто рисунки без нотации
Еще раз — в описании паттерна нет никакого «отдельного класса».

Вы русский язык понимаете?
Если вы заявляете что по определению паттерна мост необходимо колеса, фары и двигатели объединить в одном классе — «МашинаРеализация», то покажите то самое определение о котором вы говорите. Даже интересно на это посмотреть.
Но посмотрите книгу «Приемы объектно-ориентированного проектирования. Паттерны проектирования». Там прямо это не написано, но там совершенно и не написано обратное, что надо выделять абстракции и агрегировать их, там написано что в один класс надо выделить реализацию!
Это вам стоит еще раз перечитать банду четырех. Специально отыскал эту книжку, что я там вижу:
Авторы книги декларируют цель:
Отделить абстракцию от ее реализации так, чтобы то и другое можно было изменять независимо.
По вашему здесь GOF просит объединять все в одном классе «МашинаРеализация»

читаем дальше:
Абстракция окна и ее реализация помещаются в раздельные иерархии классов. Таким образом, существует одна иерархия для абстрактных классов окон (Window, IconWindow, TransientWindow) и другая (с корнем WindowImp) - для платформенно-зависимых конкретных реализаций. Так, подкласс XWindowImp предоставляет реализацию в системе X Window System.
Здесь тоже никто никого не просит ничего объединять.

Далее:
Все операции подклассов Window реализованы в терминах абстрактных операций из интерфейса WindowImp. Это отделяет абстракцию окна от различных ее платформенно-зависимых реализаций. Отношение между классами Window и WindowImp мы будем называть мостом, поскольку между абстракцией и реализацией строится своего рода мост, и они могут изменяться независимо.

Ничего не напоминает? Или быть может пример с машиной это что то иное?
«существует одна иерархия для абстрактных классов окон (Window, IconWindow, TransientWindow) и другая (с корнем WindowImp) „

Вот это WindowImp и соответствует “МашинаРеализация». Разве нет?

На это очень четко указывает наименование. Это Window и WindowImp. И совсем не Window и WindowPart1, WindowPart2 и т.д. Поэтому ваш пример с машиной и двигателем — это из совсем другой оперы. Классическая агрегация частей, а у них пример будет по сложнее, но аналогично тому, что они реализацию переносят (читать: объединяют фары, колеса, двигатели) в WindowImp.

Иногда мало прочитать — надо еще и понять.
Вам не просто нужно понять, а еще раз прочитать для начала, потому что вы ни черта не поняли о чем пишет автор потому что читали по диагонали.

Судя по вашим откровениям вам даже 6 месяцев стажа нельзя дать. Какие уж там 10 лет.
пафос… конкретика будет, или аргументов нет?
Вот вам конкретика.

Учитываю вашу заносчивость предлагаю вам публично признать что вы заблуждаетесь.
По порядку.
1. Причем ваш пример с машиной и паттерн «Мост»? Конкретно, и доказательно… или не было
Классическая агрегация подразумевает под собой, создание класса из уже существующих классов. Мост же агрегирует в себе интерфейсы, а не реализованные классы, это и есть инкапсулирование, реализацию же этих интерфейсов вы подсовываете на этапе создания экземпляра класса. Таким образом на этапе реализации класса, вы ничего не знаете, о том что именно будет агрегировано в него. Чувствуете разницу? Если нет, то я умываю руки.
Кстати как навел меня на мысль мой товарищ, мост вполне можно применять и в ФП.
Об интерфейсах там нет ни слова. Если речь в смысле IЧто-то. Агрегировать абстрактные классы или конкретные — разницы технически ни какой. Поэтому не перегружаете смыслом то, чего нет у авторов.

Разницы нет ни какой — или я агрегирую в машину двигатель, или в легковую машину двигатель внутреннего сгорания. Это обычная практика агрегирования, и на каком уровне иерархии не важно. «Мост» совсем о другом. Когда изначальна имеется ситуация, когда хочется строить иерархию (наследовать) по разным критериям… ну объяснял их пример с окнами в своей статье. Может надо самим немного «покурить» над «Мостом», чтобы понять его смысл, а уже затем читать мою статью, чтобы понять что именно там хорошо/плохо.
Вы сами совершенно не понимаете паттерн мост! Почитайте банду четырех еще раз!
об этом паттерне они пишут:
Используйте паттерн мост, когда:
Нужно избежать постоянной привязки абстракции к реализации.
Когда конкретную реализацию необходимо выбирать во время выполнения программы.
И абстракции, и реализации должны расширяться новыми подклассами. В таком случае паттерн мост позволяет комбинировать разные абстракции и реализации и изменять их независимо.
Изменения в реализации абстракции не должны сказываться на клиентах, то есть клиентский код не должен перекомпилироваться.
Количество классов начинает быстро расти, как мы видели на первой диаграмме из примера. Это признак того, что иерархию следует разделить на 2 части.
Вы хотите разделить одну и ту же реализацию между несколькими объектами, и этот факт необходимо скрыть от клиента.
И что? Что тут такого что противоречит моим словам?
Ткните меня носом в строку в которой сказано что необходимо создать отдельные классы “МашинаРеализация" и «машина абстракция». Если ткнете я публично признаюсь что я последний идиот. Если нет то это придется сделать вам.
WindowImp — Это по вашему что?
еще читайте стр. 154

Абстракция окна (Window) и её реализация (WindowImp) помещаются в различные иерархии классов.

и теперь внимание

ВСЕ операции подклассов реализованы в терминах абстрактных операций из интерфейса WindowImp.

Т.е. в методы Window реализуются через обращение только к одному классу, родителем которого является WindowImp.

Что тут неясно написано? Зачем допридумывать?
Ну или еще. Подумайте.

Там же стр. 153. Приводится синоним названия «Моста» -> Описатель/тело (Handle/Body)

а потом несколько примеров. По сути вида

class Handle
{
public M()
{
body.Method()
}
}


Как вам нотация? К двигателю из машины вы так бы обращались бы? Нет. Наличие body предполагает его единственность, т.е. в body содержаться и методы работы с двигателем, фарами и колесами. В противном случае, это не называлось бы Handle/Body.

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

Опять не верите? Но объясните хотя бы себе — почему называется «Handle/Body»
Или еще. Совершенно замечательный рисунок на стр. 154.

код вида

DrawRect()
{
imp->DevDrawLine()
imp->DevDrawLine()
imp->DevDrawLine()
imp->DevDrawLine()
}


И может быть знаете, такое правило рефакторинга: если в методе подавляющие большинство строк обращается к чужим свойствам/методам — это указывает на то, что метод занимается не своим делом. И нужно применять рефакторинг вида выделять/спускать метод. Т.е. в итоге этот паттерн «Мост» дурно пахнущий, и при первом же рефакторинге его нужно снести. Или полностью внеся объект imp внутрь. Или наоборот, понять чем занимается imp конкретно, и тогда наоборот DrawRect перенести в уже нормальный класс с нормальным именем, а не как тело чего-то.
Вы обсуждаете не «мост» а pimpl с единственной реализацией.
Бывает, что иерархия того на что указывает imp поставляется с библиотекой, которую нет возможности модифицировать. К тому же введение нового метода в базовый класс/интерфейс ухудшит его инкапсуляцию.
Ну и лично я не тут вижу «зависти» по Фаулеру, обычный вызов методов через интерфейс базового класса, разве их как-то по другому нужно использовать?
Не бывает, imp — это объект, который мы сами выделили из абстракции. Ну, если не видите «зависти», что тут делать… смотрите еще. Что значит базового класса? Базовым обычно называется класс родитель, а тут это наследование заменено агрегацией
Очень даже бывает, не всегда при проектировании используется декомпозиция, бывает еще и обобщение используют ;)
Я имел в виду что imp это указатель на базовый класс, через его интерфейс происходит работа с объектом.
Итого. Пока обсуждали не статью. Все что пока обсуждали — это не верное понимание паттерна «Мост». Сделал соответствующие дополнение к статье. Надеюсь дальше будем обсуждать именно паттерн «Мост», а не применение интерфейсов или агрегацию абстрактных классов. Ну не про это это. Приведенные читателями примеры — хорошие и правильные, но они просто не содержат в себе паттерна «Мост». Честно. Давайте хотя бы одинаково понимать, что есть «Мост» — тогда и понимание статьи придет.
>> Итого. Пока обсуждали не статью. Все что пока обсуждали — это не верное понимание паттерна «Мост»
Совершенно верно подмечено, к «Мосту» эти комменты не имеют отношения.

>> Я прочитал эту статью о паттерне проектирования «Мост». Увы, его очень часто используют не верно.
Возможно, но к статье про будильник это высказывание не имеет отношение.
В той статье мост не был необходим, если рассматривать только в контексте приведенной автором диаграммы и упустить, что это лишь «проверка пройденного материала». Автор статьи с будильником применил мост совершенно правильно, не с точки зрения конечной необходимости, а с точки зрения расширяемости как абстрактной части, так и реализации.

Но здесь нужно уточнить, что же это за «абстракция» в контексте «Моста» у авторов книги и автора статьи с будильником.

Посмотрим на ваш пример с C++.
h-файл здесь никакая не абстракция вообще, даже с точки зрения классики, а это описание (объявление свойств и методов) класса, что связано с особенностью языка C++.
cpp-файл — это реализация объявленных методов, а не реализация абстракции, что, опять же, связано с особенностями языка.

Абстракция в классике (абстрагирование, он же abstraction) — это выявление общего у нескольких объектов с целью группировки. В языках программирования абстракция может быть выполнена с помощью абстрактного класса или же с помощью интерфейса.

Абстракция в контексте «Моста» и «будильника» — это бизнес логика приложения. Это не набор интерфейсов или абстрактных классов, а верхняя часть программы.
Реализация в контексте «Моста» и «будильника» — это низкоуровневое общение с системой. Это общение может происходить совершенно по разному, в зависимости от системы, но тут вступает в силу прослойка «Мост»:
— в примере из книги — это «WindowImp»;
— в «будильнике» — это «AlarmClockImpl».
Обе эти сущности могут быть либо интерфейсом, либо абстрактным классом (это уже зависит от степени схожести низкоуровневых систем). Эта прослойка необходима для получения единого интерфейса взаимодействия между высокоуровневой частью приложения (бизнес логикой) и низкоуровневой (окружения системы).

Дальше, в зависимости от системы, появляются различные реализации этого самого класса (самого «Моста»).
— в книге это MacWindowImp (для мака), PMWindowImp (для PM), XWindowImp (для X Window);
— в «будильнике» — ShellMP3AlarmClock (для вывода музыки через командную строку, к примеру play), SystemMP3AlarmClock (для вывода музыки с помощью системных библиотек);

Реальные примеры, где это может быть использовано:
1) драйверы оборудования, к примеру, жесткий диск, практически каждый производитель предоставляет драйвер, который является ни чем иным, как реализацией этой самого интерфейса прослойки (другими словами «Моста»);
2) плагины для проигрывателя музыки или видео — плагин это тот же драйвер, который позволяет читать файл с определенной структурой. Бизнес логика приложения практически не привязана к формату файла, а просто подставляет нужный плагин (драйвер).
«Автор статьи с будильником применил мост совершенно правильно»

Да, согласен. Как минимум в том смысле, что его ситуация (в отличии от примеров выше) как раз требовала применение «Моста». Я вообще-то ожидал, что паттерн «Мост» будет понят однозначно. Поэтому фраза «Увы, его очень часто используют не верно.» указывала скорее на то, что «автор будильника» достаточно произвольно разделил абстракцию от реализации. И это конечно, частность… но она типовая, и порождена на мой взгляд плохим описанием паттерна у авторов.
>> И это конечно, частность… но она типовая, и порождена на мой взгляд плохим описанием паттерна у авторов.
Да, но достаточно присмотреться к приведенным в книге диаграммам, чтобы понять, что такое этот WindowImp и зачем он, отрываясь от контекста примера.

Суть моста как раз в выделении общего у низкоуровневых систем, с которыми нужно работать и создание общего достаточного интерфейса (абстрактного класса), который все «плагины» должны будут реализовать (наследовать), чтобы интерфейс взаимодействия верха с низом оставался прежним.

Тут как бы два берега:
— бизнес логика — верх;
— окружение системы — низ;
а между ними «Мост» — интерфейс передачи
«Тут как бы два берега:
— бизнес логика — верх;
— окружение системы — низ;
а между ними «Мост» — интерфейс передачи»

еще чуть-чуть и вы опишите MVC (Model-View-Controller), утрирую конечно :)

Но до этого доводить нельзя, это другая красна линия «сверху». Мотивирование использования «Моста» все же проще…
да, у меня тоже промелькнула эта мысль, когда писал, но все же так нагляднее
MVC — другая модель с другими целями.
И вообще, паттерны определяются в первую очередь в способом их реализации, а семантикой. К примеру, паттерны «Стратегия» и «Состояние» реализуются одинаково, однако разница в семантике определяет и название, и назначение. Поэтому похожесть структуры на часть MVC — это еще ничего не значит. Семантика другая, назначение и название, соответственно, тоже.
Согласен, что разница в семантике. Именно это тут объясняю. Но с MVC там сложнее, там есть и семантическое подобие.
И еще по поводу множественных иерархий.
Ничто не мешает провести два моста от одной иерархии. И проблемы нет. Паттерн «мост» — это инструмент, абстракция, которую можно использовать — а можно и не использовать, как в Вашем случае. Можно вообще все только на наследовании и агрегации описывать. Только вот паттерны обертывают классы в более понятные структуры, которыми легче оперировать. В результате они снижают сложность восприятия. А, следовательно, с их помощью можно создавать более сложные системы. А без их использования — менее сложные, т.к. у человека есть предел сложности, который он может воспринимать.
Так что, ИМХО, Вы просто не понимаете смысла паттернов, как он описан у GoF. Это не мешает Вам их использовать: вот оно — отличное описание у GoF! Даже те, кто не понимает концепта, может это использовать.
Дело как раз в том, что все эти паттерны я использовал ДО ТОГО, как узнал о том, что их именно так называют. Все что сделали GoF назвали это умными словами. Но как я начинаю понимать расхождение скажем между GoF и Бучем в самых основах, GoF очень много притянули из процедурного программирования, и именно с теми паттерами, где идеология процедурного программирования по семантике ставится во главу угла — я и не согласен, они противоречат ООП. Но многие или не замечают, или смешивают, то что нельзя смешивать, т.к. страдает архитектура ПО от таких паттернов в ОО-коде.
Основная концепция ООП — снижение сложности ПО.
Если эта цель не достигается — все остальное идет в топку, т.к. с этим становится очень сложно жить.
Вот. Спасибо за классическое описание того, что есть «мост». Полностью с вами согласен в этом смысле. Единственно наверное вы более точны, когда пишите

«Абстракция в контексте «Моста» — это бизнес логика приложения. Реализация в контексте «Моста» — это низкоуровневое общение с системой.»

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

Но с вашей формулировкой абстрагирования не соглашусь. Г. Буч писал о другом, да и по сути до него это всегда понималось так. Абстрагирование — это не выявление общего. Абстрагирование это избавление от сложность реального объекта, путем редукции его свойств.

Вот выдержка из Буча
"«Люди развили чрезвычайно эффективную технологию преодоления сложности. Мы абстрагируемся от нее. Будучи не в состоянии полностью воссоздать сложный объект, мы просто игнорируем не слишком важные детали и, таким образом, имеем дело с обобщенной, идеализированной моделью объекта» [36]. Например, изучая процесс фотосинтеза у растений, мы концентрируем внимание на химических реакциях в определенных клетках листа и не обращаем внимание на остальные части — черенки, жилки и т.д. И хотя мы по-прежнему вынуждены охватывать одновременно значительное количество информации, но благодаря абстракции мы пользуемся единицами информации существенно большего семантического объема. Это особенно верно, когда мы рассматриваем мир с объектно-ориентированной точки зрения, поскольку объекты как абстракции реального мира представляют собой отдельные насыщенные связные информационные единицы."

Поэтому когда я писал о .h файлах, а естественно упрощая, подразумевал, что в них уже выделены основные важные для нашего рассмотрения свойства/методы (описание/поведение) объекта. И именно, спецификация объекта дает наиболее полное понимание абстракции.

Но авторы (или это проблема перевода) скорее действительно вкладывают смысл более близкий к разделению на бизнес-логику и низкоуровневое общение.

Но это лишь дает мне еще больше карт для критики этого паттерна. Завтра напишу.
Ну Вы же сами подтвердили мое краткое определение абстракции выдержкой из Буча:
«Люди развили чрезвычайно эффективную технологию преодоления сложности. Мы абстрагируемся от нее. Будучи не в состоянии полностью воссоздать сложный объект, мы просто игнорируем не слишком важные детали и, таким образом, имеем дело с обобщенной, идеализированной моделью объекта»

Как только объект становится обобщенной моделью, сразу появляется группировка по это принципу (систематизация, если хотите)
— легковая и грузовая машины тоже машины, если абстрагироваться от некоторых их «сложных для понимания» свойств;
— с другой стороны машина и запряженная повозка это средства передвижения, если абстрагироваться от других их отличий;
В том то и дело, то что вы описываете это обобщение (в логике родовые-видовые понятия, закон убывания содержания с увеличением объема). А обобщение — это лишь один из способов абстрагирования.

Например, возьмем самый конкретный объект — легковую машину модели Мазда 3F. Но вот она есть в реальности, а программируя бухгалтерскую программу, нам будет интересна лишь её стоимость, амортизация и еще кое что из денежных показателей. Это будет абстракция №1. Но если мы запрограммируем машину для техобслуживания, нам будут важны другие критерии — состав и состояние деталей, на сколько они изношены, и что нужно исправлять и как это сделать. Это абстракция №2. И между этими абстракциями нет ни каких отношений обобщения, она появилась ни как результат от обобщения, а как описание интересующего нас взгляда на реальный объект.
да, согласен, мое определение абстракции утрировано до обобщения, ваше более подходит для задач программирования
Также я немного утрировал «абстракцию» в «Мосте» до бизнес логики приложения.
Если углубиться, то это просто более верхний уровень программы, нежели «реализация», которая зависит от внешних условий, не особо влияющих на данную «абстракцию».
Именно. В утрированном смысле я расписал ниже. А более общему, когда просто выделяется из бизнес-логики другая бизнес-логика, но по тому или иному критерию — это я и пытался донести статьей. Увы, опыт у всех разный, и написать понятно не легко. Но я старался :)
Попробую набросать черновик.

Идея паттерна «Мост» (если действительно понимать, +1 Mavim, остальные пока минус) понятна и существует. Но обсуждаемая статья, это начало, чтобы показать, что применение «Моста» — это скорее некий «костыль», недоделанная «затычка» в программе.

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

Вот тут попробую заполнить пробел.

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

Сам стиль программирования когда это перемешено очень плох и не предполагает архитектуру. Вот в таком коде применение «Моста» просто должно обострить наше внимание. Это просто позволит разделить бизнес-логику в одном классе, а то, что было подмешано в ней низкоуровневого вынести отдельно.

Но если бы вы писали код сразу продумывая архитектуру, то вам бы не пришлось применять паттерн «Мост», и вы бы реализовали бы себе просто некий набор SDK. Т.е. выделили бы абстракции низкоуровневых классов: работа с файлами, с базой данных, сделали бы оболочки для всех визуальных элементов, и все где были бы завязки на особенности производителя сделали бы свою надстройку.

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

И тогда окажется, что в бизнес-логике — тут надо не только работать с файлами, но и с базами, а еще и формы показывать. Т.е. нужна просто среда/каркас, которая позволяет легко из бизнес-логики вызывать низкоуровнневые функции. И сами эти функции нечего мешать с бизнес-логикой, выделяйте их по назначению. Или если лень разрабатывать каркас, то можно проще. В классах бизнес-логике создавайте нужные объекты работающие с низким уровнем, и используйте эти объекты где надо. Но не как тело.Метод1()+тело.Метод2(), а как ФайловыйМенеджер.МетодА()+БазаДанных.МетодБ().

Но это повторюсь особый взгляд на ситуацию «Мост», для разделения бизнес логики и низкоуровневой части. В статье обсуждается другой взгляд как необходимость заменить множественное наследование. Но принципиально рецепт тот же.
Вот и кстати, как пример перемешивания бизнес-логики и низкоуровневых функций. Выше я писал, что в бизнес логике нельзя использовать такую частность как прямую работу с базой данный через select и прочие. Нужно работать через хранимые-процедуры, а технику вызова кодировать в определенном своем классе, иначе переход с MS SQL на Oracle — будет не просто гемором, но и просто невозможен. Все вызовы будут раскиданы по коду, и невозможно ничего менять в логике базы без связи с приложением. Это там мелкий, но важный пример.
в данном случае, на мой взгляд, лучше ORM + DBAL или, на крайняк, DAO + DBAL
Их тоже надо абстрагировать в СВОЕМ классе, всюду обращаться к нему, а уже внутри решать какую технологию использовать. Иначе устареют и зависнешь.
В одном проекте мы завязаны на ADO.NET, но вот уже там есть разные подходы сделать одно и тоже. Эволюционируется все :)… поэтому мы сделали свои прокладки ряда необходимых объектов/методов, забраковав целую кучу лишних/неверных по нашему убеждению подходов. И вот эти прокладки, а не прямые вызовы дают стабильность развития. Но это уже больше как offtopic
Вы умело вырываете фразы из контекста. Мой пример этот вовсе не пример перемешивания логики, это просто пример, если хотите псевдо-код отражающий общий смысл.

Вот кстати цитата из той же книги.
Все, - теперь клиенты нашей системы могут свободно запускать данные команды добавления и редактирования в контекстах файлов или базы данных:
Обратите на код в методе main – это то, что и достигается применением данного паттерна: независимый, единый код запуска любых видов команд в любом контексте, который не нужно будет менять и перекомпилировать при каких-либо изменениях в деталях реализации тех или иных операций, алгоритмов выполнения команд и т.д.
Конкретные реализации clientUpdateImpl и balanceCorrectImpl для простоты здесь определяются напрямую, но как говорилось выше часто бывает полезно получать их динамически в конструкторе команды (Command) из абстрактной фабрики.


Прокомментируете?
страницу книги указывайте
Кодировать в отдельном классе вызов хранимых процедур или вызов SELECT * FROM table — в любом случае этот класс будет мостом между остальным приложением и базой. Хранимые процедуры в предлагаемых вами целях — это ещё один мост, их вызов в отдельном классе — мост к мосту. Не перебор ли?
Нет, вы путаете тут мост совсем не причем. Это использование паттерна «Адаптер» (думаю напишу про это отдельную статью, если будет востребовано). Про хранимые процедуры, разговор зашел случайно. а выше я примерно тоже пишу
Адаптер, по-моему, подразумевает приведение интерфейса одного класса к интерфейсу другого. Или приведение интерфейсов нескольких классов к одному интерфейсу. Как правило в случае если интерфейсы устоялись. Без изменения уровня абстракции. Грубо говоря, чтобы можно было использовать Db::query вместо Mysql::query или Pgsql::query.

В случае же отдельного класса/иерархии классов для абстракций вроде UsersStorage::getList и PostsStorage::getById, как правило часто изменяющихся (хотя бы расширяющихся при добавлении новых ), и наличия нескольких сильно различающихся конкретных реализаций (несколько SQL, несколько NoSQL, файловые и т. п.), которые тоже часто изменяются независимо от изменения абстракций и друг друга (оптимизации, включая денормализацию, прозрачное для клиентов кэширование, поддержка фич разных версий и т. п.) по-моему об адаптере говорить сложно. Классический мост, чтобы избежать появления классов типа MysqlUsersStorage и FileUsersStorage.
Про адаптер вы пишите верно. Верно также то, что надо избавляться от MysqlUsersStorage, FileUsersStorage. Но делать это надо БЕЗ моста.

Вот посмотрите вы приводите пример UsersStorage::getList и PostsStorage::getById и Mysql::query или Pgsql::query. Разница какая? Как я пониманию разница не в этом, а в довольно мутном описании: «часто изменяющихся… сильно различающихся конкретных реализаций» — но и для (Mysql::query или Pgsql::query) часто изменяются, и сильно различаются. Тут разницы нет. Поэтому речь именно о адапторе. Возможно вы подразумеваете, что бывают ситуации сложнее, но тогда есть и другие паттерны, кроме Адаптера — Стратегия, Посетитель, Наблюдатель… они в комбинации заменят Мост с лихвой, но не будет нарушения ООП, невозможности повторного использования, отсутствия гибкости…
Мне кажется в пункте 2 дополнительных пояснений вы указали как раз «Мост». Тонкой нитью через все паттерны проходит идея инкапсулировать изменяющиеся части программы. Данный паттерн предлагается использовать когда изменениям подвержена какая-то часть реализации класса.
По вашему «Мост»=«Использование интерфейсов». Ну, зачем вам что-то объяснять? Просто еще подумайте разок: они что выучили, что такое интерфейсы и решили свое объяснение назвать «Мост»? Классные специалисты, завтра выучу, что такое наследование, объясню это и назову, ну пусть «Подмостки» :)

Зачем вводить новый термин, без увеличения назначения программных конструкций? Я понимаю, что интерфейсы это круто :), но зачем это называть «Мост»?
Да, «Мост» использует интерфейсы/базовые классы, это видно из его UML-диаграммы. Знаете, интерфейсы можно использовать по-разному, конкретно «Мост» использует их для инкапсуляции части реализации, да, технически тут нет ничего хитрого, обычное использование интерфейсов.
А по вашей логике тогда и «Абстрактная фабрика»=«Использование интерфейсов».
Так вот инкапсулировать реализацию путем введения излишнего класса без семантической нагрузки — противоречит ООП. А мост именно это и делает.
Как это без семантической нагрузки? :-)
На примере визуальных окон.

Window — абстракция окна
WindowPrimitive (у GoF WindowImp) — абстракция ОС-зависимой реализации примитивов для создания окон.
IMNSHO, как раз таки очень понятная семантическая нагрузка.
А где теперь уже не модно стало множественное наследование? Можно почитать? Я что-то, видимо, отстал от тенденций.
Множественное наследование, как множественное наследование, а не извращения типа Интерфейсов, которые на практике нифига не множественное наследование, т.к. они наследуют только интерфейс, но не поведение
Множественное наследование подразумевает нарушение принципа единственной ответственности. Где он «моден» — там множественное наследование не модно :)
Спасибо, действительно — это прямо в точку.
есть такая штука, поэтому во многих языках отказались от множественного наследования вообще
Я откровенно не понимаю, как, скажем, Interface в Java помогает избавиться от этой проблемы.
Ну, как. Вначале нужно понимать когда использовать множественное наследование, а когда нет. Тут я описывал, что такое абстрагирование. Так вот когда, у нас есть Машина, и она нам интересна не в одном, а в нескольких аспектах, например, МашинаКакТовар и МашинаКакУстройствоДляПроверкиНаТехсервисе, то говорят, что Машина выполняет несколько ролей, две в данном случае. И вот когда объект играет более, чем одну роль используется множественное наследование. И только в этом случае.

Теперь, как интерфейсы позволяют избавится от проблемы ромба. Очень просто. Создаются два интерфейса IТовар и IУстройствоДляПроверкиНаТехсервисе. Определяется, что должны выполнять эти интерфейсы. Но реализация этих двух интерфейсов находится в одном объекте Машина.

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

У меня есть два интерфейса. IFirst, ISecond. Они заставляют меня реализовывать метод «run» без параметров. Как обойти эту проблему в классе наследнике? Это даже не так проблема ромба, но всё та
К сожалению, непонимание этой воды — приводит к множеству ошибок.

А зачем Вы хотите «наследоваться» (а точнее, выполнять) от IFirst, ISecond, если не хотите реализовывать метод Run()? Не «наследуйтесь» от не нужных вам интерфейсов. Наверное, вы имели введу какую то более сложную ситуацию, тогда сформулируйте её.
Как я понял проблема не в том, что «не хочу реализовывать», а что интерфейсы IFirst и ISecond придают методу run() различную семантику при одинаковой сигнатуре. Проверки типа instanceof(IFirst) дадут разрешение на вызов метода run() с семантикой подразумеваемой IFirst. Но вот что действительно вызовется…
Ах вот в чем дело, т.е. метод Run() и в интерфейсе IFirst и в ISecond. Но не знаю как в Яве, но в C# реализация методов IFirst.Run() и ISecond.Run() закрытая, т.е. с модификатором private. И если вы обращаетесь к классу ABC, который реализует эти два интерфейса Вы не сможете вызвать методы Run. А чтобы его вызвать вы должны получить из класса ABC, требуемый вам интерфейс, т.е. ((IFirst)ABC).Run() или ((ISecond)ABC).Run(), тем самым кто будет использовать тот и определит каким интерфейсом ABC он хочет пользоваться, и будет с ним и работать дальше.
Причем, в хорошо организованной программе привидение не будет делаться для каждой строки. А скажем в класс Товар будет требовать, чтобы ему передали не Машину, а МашинуКакТовар, т.е. уже в конструктор пойдет передача (IFirst)ABC, там переприсвоится ссылка IFirst MyFirst, и дальше по всему объекту работаем с MyFirst, в то время как на ТехСервисе работаем аналогично с ISecond
А если реализовывается только один интерфейс, то тоже нужно явное приведение?
Да, но в этом нет ни каких проблем. Тут надо просто решать на уровне проектирования, или использующих объект интересуется самим объектом вообще, или его ролью (пусть и одной) — и дать ему или ссылку на объект, или только роль. Вообще вариантов много как сделать — но это уже на уровне проектирования связей между объектами.
Ну, и затем если интерфейс один, и интерфейсы применяются в смысле множественного наследования — это тревожный знак — а множественности то и нет, нужно одиночное наследование.
Вообще, tac вас вводит в заблуждение, в c# явное приведение для этого не нужно, для этого нужно, чтобы объект, через который идет обращение, был объявлен, как объект соответствующего интерфейса. Если быть точным: «the member can be accessed only by using a reference to the interface».

Соответственно, если у вас есть объект другого типа, то нужно из него вытащить ссылку на интерфейс (это не совсем приведение); если у вас это параметр, то обычно его сразу объявляют нужного типа (следуя макконеловскому most generic type), и там этой проблемы просто нет.
А чтобы его вызвать вы должны получить из класса ABC, требуемый вам интерфейс, т.е. ((IFirst)ABC).Run() или ((ISecond)ABC).Run(), тем самым кто будет использовать тот и определит каким интерфейсом ABC он хочет пользоваться, и будет с ним и работать дальше.

VolCh меня правильно понял, вы правильно ответили, спасибо.
А в чём проблема так же поступить с обычным множественным наследованием?
Ну или, как минимум, в чём проблема использовать множественное наследование так, как в php?
«Но не знаю как в Яве, но в C# реализация методов IFirst.Run() и ISecond.Run() закрытая, т.е. с модификатором private.»
Вообще-то, это называется explicit, и модификатор private там поставить нельзя. И только в том случае, если вы это явно сделали.
В ряде более сложных ситуаций делается так. Создается абстрактный класс, который реализует нужные интерфейсы, скажем IFirst, ISecond. Причем реализует очень специфично — вводится чисто заглушка, т.е. сигнатура методов, если нужен возврат возвращаем null, свойства (get, set) возвращают null/ничего не устанавливают, методы тоже ничего не делают.

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

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

А правильное разделение спецификаций интерфейсов — это умение, и пока не будешь понимать воду — будут проблемы.
Это не так. множественное наследование само по себе не нарушает принципа единой ответственности.
Нарушает его программист при помощи этой возможности языка.
С таким же успехом можно сказать, что добавление методов в класс нарушает принцип единственности ответственности.
Вообще, я больше имел в виду, что не множественное наследование нарушает принцип единой ответственности, а именно применение паттерна «Мост» в том, смысле как рекомендую авторы паттерна — «отделение абстракции от реализации» — это разделение ответственности на несколько частей, причем не разделение разных ответственностей, а именно одной. А множественное наследование, это просто одна из техника которая может применяться в место «Моста» в аналогичных ситуациях.
Тут всё сложнее.
Не хочется применять мост, не нужно, тут в рамки никто никого не загоняет.
На применение моста есть свои ограничения.
ИХМО, это один из самых сложных партенов.
в Том же GoF прописано зачем они это делают и когда имеет смысл делать это, а когда не стоит.

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

у нижней — ответственность реализации (которых может быть много)
пока общие слова, ниже я попросил ответить конкретнее, на ваше примере — какая ответственность у Машины, и какая ответственность у «ФизикиМашины»? А заодно вспомните о том, что такое роли объектов в ООП, после вашего ответа ниже — это станет важно.
Скажем так, множественное наследование чаще всего используется для предоставления нескольких ответственностей классу. Настолько часто, что другого применения навскидку и не вспомнить :)
если речь идет о С++, то это может быть наследование от нескольких интерфейсов.
ведь в С++ нет интерфейсов, там только классы
В последнее время всё больше убеждаюсь в том, что самым главным принципом ООП является вовсе не один из наследование, инкапсуляция, полиморфизм и абстракция, а вот делегирование на самом деле.
Без наследования — это уже просто ОП, а не ООП.
Кстати, VBA, помнится, как раз из этой категории.
Я не говорил, что без наследования нужно обходиться, просто грамотно использовать делегирование (в т.ч. и вместо наследования) важнее, хотя оно не считается основным принципом ООП.
Ммм… По моему автор не совсем понимает зачем нужен Мост. Мост — это костыль. Это когда надо связать несвязываемое. И, как мне кажется, вы подменяете местами понятия абстракция и декларация.

Дело в том что абстракция, даже в ООП, это понятие поведения и свойтсв объекта не в плане того как они называются и как их вызывать, а что они делают и что показывают.
Декларация — это описание поведения и свойст предмета именно в плане того как их вызывать и как они называются.

Т.е. у меня есть абстракция будильник. Он умеет будить и у него можно задать время. Как у него называется метод который меня разбудит мне вообще по барабану. Мне важно его поведение и свойства. Далее есть плейер. Он умеет проигрывать музыку. У него в декларации нет метода «Будить», но есть метод «Проигрывать». Так вот, когда я использую плейер как будильник, то я его использую именно с целью разбудить, т.е. меня интересует не то как у него метод называется, а то, что он сделает для меня полезного в рамках моей абстракции будильник. Я работаю с абстракцией, а не с декларацией. Точка.

Мост нужен для того что бы отделить абстракцию от реализации. Вам нужно использовать ваш плейер как будильник, но плейер не будильник. Абстрагируясь я могу представить что плейер — это будильник. Он делает теже вещи что и будильник. Задача проектирования как раз и сводится к тому, что бы мы работали с абстракциями, а не с реализациями.
Это было про абстракцию.

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

«применение «Моста» — это скорее некий «костыль», недоделанная «затычка» в программе.»
тут

А далее пишу, что этот костыль снесется первым же рефакторингом. Поэтому не лучше ли сделать сразу хорошо? Невозможно? Вынужденно? НЕ ВЕРЮ…
Да дело не в мосте. Не в том что он плохой или хороший. Когда это оправданно — это надо использовать.

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

А вот гарантирую, что случая когда «Мост» надо использовать — вы не найдете. В том то и дело.
Это может быть лишь промежуточным решением.
Ваша ошибка в том, что вы опускаете Bridge до уровня класса. Поднимитесь на уровень системы и сразу все поймете.

Есть система А, она хочет, чтобы ее будили в заданное время, есть система Б, предоставляющая такие услуги. Их разрабатывают вообще разные люди. Это все надо заинтегрировать. Поскольку и та, и другая система находятся в стадии разработки (это вытекает из определения паттерна), и меняются несинхронно, мы пишем прослойку, разные концы которой меняются в соответствии с изменениями соответствующей системы.

Все.

ООП вообще не при чем.
Да не важно класс, или система. Суть одна.

«Есть система А, она хочет, чтобы ее будили в заданное время, есть система Б, предоставляющая такие услуги.»

Это не о «Мосте». Если же о «Мосте», то так
«Есть система А, она хочет, исполнять роль будильника и обеспечивать будильником клиентов, есть система Б, предоставляющая такие услуги.» То надо не так строить разделение на систему А и Б. А или (1) объединить системы вместе (почему их должны разрабатывать разные люди, когда функционально эти системы выполняют одно и тоже). Или же (2) система Б на самом деле не все делает, что нужно, а поэтому вводится система А, как расширение. Но тогда нужно понять что система Б делает, и это использовать, как часть, возможностей системы А.
Почему это не о мосте? Аргументируйте.

Дополнительно:
«Есть система А, она хочет, исполнять роль будильника и обеспечивать будильником клиентов»
Какие клиенты? Нет никаких клиентов, система А — тупиковая.

«почему их должны разрабатывать разные люди»
Потому что каждая из них разрабатыватеся разными компаниями уже 10 лет.
Если нет клиентов, то зачем вам будильник? Клиенты, это программисты, которые будут использовать систему А.

Если системы разрабатываются разными компаниями, то нужно использовать паттерн Адаптер.
«Клиенты, это программисты, которые будут использовать систему А.»
В рамках архитектуры программисты не рассматриваются, уж простите.

«Если системы разрабатываются разными компаниями, то нужно использовать паттерн Адаптер. „
Адаптер используется тогда, когда есть два стабильных интерфейса.
1. Удивительно, а зачем же тогда создается архитектура? Для кого?

2. Наличие/отсутствие каких-то программных конструкций не определяет, использовать или нет паттерны. Нужно, что бы возникла смысловая ситуация.
«Удивительно, а зачем же тогда создается архитектура? Для кого?»
Для ряда заинтересованных лиц. Однако в саму архитектуру они не входят.

«Наличие/отсутствие каких-то программных конструкций не определяет, использовать или нет паттерны. Нужно, что бы возникла смысловая ситуация. „
Вот фиксированность интерфейса — и есть определяющая часть вашей “смысловой конструкции».
А я говорил, что разработчики входят/включаются в архитектуру? Но они рассматриваются, в диаграммах прецедентов, т.е. в проектировании. Если же переходить на уровень кода/программирования, то это SDK/каркас/класс системного слоя которые используются разработчиками для решения задач бизнес-логики. Так понятно?

Второе не серьезно обсуждать.
«А я говорил, что разработчики входят/включаются в архитектуру? Но они рассматриваются, в диаграммах прецедентов, т.е. в проектировании. Если же переходить на уровень кода/программирования, то это SDK/каркас/класс системного слоя которые используются разработчиками для решения задач бизнес-логики. Так понятно?»
Простите за грубость, у вас тут мешанина. Когда мы говорим о паттернах, нас не интересуют диаграмы прецедентов. И даже SDK/framework нас не интересуют. У нас есть две системы, взаимодействующие по определенному принципу. Все остальное не представляет интереса для рассматриваемой задачи.

«Второе не серьезно обсуждать. „
Отнюдь. Собственно, именно это написано в определении паттерна:
“The BridgePattern decouples an abstraction from its implementation so that the two can vary independently. This is unlike the intent of the AdapterPattern, which exists only to adapt the interface of one class to another. » (http://c2.com/cgi/wiki?BridgePattern)
«Все остальное не представляет интереса для рассматриваемой задачи.»

ВОТ ОНО. А я то думал откуда идут заблуждения 80% комментирующих (кроме 3 обозначенных выше, начавших серьезное обсуждение). Если бы это не было важно, то все паттерны были бы одним и тем же. Но в них важно не техника взаимодействия объектов, а причина/назначение почему они должны так взаимодействовать. А это как раз видно из диаграмм UML, и неоткуда больше, из кода это не вычленишь.
Вы путаете уровни абстракции.

Дизайн-паттерны существуют на уровне абстракции кода. На этом уровне взаимодействуют системы и их элементы.

А SDK и программисты существуют на уровень (или несколько) абстракции выше, где взаимодействуют бизнес-пользователи и прикладные системы.

Соответственно, у классов (и компонентов), несомненно, есть предназначение и ожидаемое поведение (кстати, если его нельзя вычленить из кода, а обязательно нужна диаграмма, то это в среднем плохой код). Но это не имеет ничего общего с бизнес-предназначением системы, и на уровне анализа паттерна и его применения можно вообще не знать, зачем, как и через сколько посредников это место будет использоваться бизнес-пользователем.
Так вот если считать, что паттерны проектирования существуют на уровне техник программирования — это и будет существенной ошибкой. Это даже из названия «паттерны проектирования» видно. Несогласны подтверждайте ссылками на литературу — цитатами.
«Это даже из названия «паттерны проектирования» видно.»
Почитайте МакКонела. У него явно сказано, что дизайн может быть на любом уровне, в том числе и процедуры (например).

(Странно говорить об этом в блоге Complete Code)

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

Это вот это совершенно не серьезно, если вы так считаете — то лучше вообще не заниматься проектированием ПО. Кодируйте Шура, кодируйте
Задача — большие картинки, не влезающие в память.

Есть абстрактный класс BigImage, у него подклассы BmpBigImage и GifBigImage, методы Load() и Show().
Есть абстрактный класс BigImageImp, у него подклассы WinImage и Os2Image, методы LoadImageFromFile() и PaintImage().

В иерархии BigImage зашиты алгоритмы работы с форматами GIF и BMP и определение участка, необходимого для текущей обработки, подготовка абстрактного представления картинки для вывода в соответствии с форматом изображения.
Иерархия BigImageImp занимается эффективностью операций работы с файлами для конкретной ОС и отображением (части) картинки заданного формата нативными средствами.

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

Опишите, как Вы это реализуете без моста )
> Паттерны — это лишь частное и не всегда самое удачное решение на базе ООП принципов.
ИМХО более правильно былобы сказать, что большинство т.н. «паттернов» это не более чем применение принципов SOLID к проектированию. Сами по себе базовые принципы ООП (не знаю, почему именно они названы базовыми и почему именно для ООП. Ведь инкапсуляцию и делегирование — это достаточно общие принципы и не являются эксклюзивными для ООП) не обязательно приведут к «хорошим» решениям.

А так — с мыслью в заголовке согласен.

В качестве продолжения примеров надуманности т.н. «паттернов» можно рассмотреть как обычная зависимость благодаря применению принципов SOLID сначала превращается в «паттерн заместитель», а после по мере развития системы — в «паттерн цепочка обязанностей», и в особо запущеных случаях «кровавого энтерпрайза»- в «паттерн бизенсс-делегат»
давайте попробуем, накидайте пару идеи подробнее, я попробую добавить и получится еще одна статья…
Ох, тут завязались такие баталии.

Хочу добавить от себя пару замечаний.

Перечитал по этому кучу популярных книг GoF, Макконел, Фаулер ....(и ещё штук 20)

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

1. чем крупнее абстракция, тем лучше (уровень «крупноты» выбирает программист на основе задачи и мироощущении — есть даже рекомендации как это сделать, читайте Макконела)
2. чем проще абстракция, тем лучше

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

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

Классический пример в GoF с редактором и разными оконными системами.
Двигатель с машиной, который обсуждался раньше — пример менее удачный. правильнее было б вводить вводить абстракцию машина (стоит, движется, имеет скорость) и делегировании физики машины в конкретную модель. Зачем полезли во внутренности и прочие вещи, я так и не понял.

Своих примеров приводить не буду, их до меня было достаточно.

А теперь от теории к практике, применял мост в паре сложных проектов(с нуля и при рефакторинге), удалось значительно упростить код и сократить его раза в 3 где-то.

Автору статьи рекомендую сначала перечитать макконела совершенный код, потом SOLID принципы (о которых кстати, говорится в макконеле), а потом уже перечитывать GoF.
эффект «как бы произведения абстракций» — все хорошо, хотя бы по назначению. Но статья как раз о том, что это можно делать по другому, и это будет еще БОЛЕЕ эффективно и БОЛЕЕ ООП-правильно.
в чем эффективность?
в чем правильность?
то, предлагаете заменить тот же мост простым множественным наследованием.
или я чего не понял?
Нет, множественное наследование это то, как можно решить ситуацию возникающую в ситуации использования «моста». И ее раньше так решали, была даже такая идеология «примесей». Но проблем в множественном наследовании больше. Поэтому это тоже не вариант. Но правда уже по другим причинам.

Про эффективность и правильность — поговорим ниже, если согласитесь подискутировать по основам ООП, вопросы ниже.

P.S. т.к. меня отхабрили, то стало не удобно отвечать — раз в 5 минут, а потом видимо будет раз в час. Поэтому если желаете говорить используйте почту местную или e-mail.
«правильнее было б вводить вводить абстракцию машина (стоит, движется, имеет скорость) и делегировании физики машины в конкретную модель»

А вот это ОО-неправильно..В смысле авторы паттерна «Мост» именно в таких случаях это и предлагаю. Но отделять физику машины ООП-неправильно. Это недостаточно точно декомпозирует систему.
почему это не соответствует принципам ООП? сошлитесь на литературу откуда вы это взяли.

«Это недостаточно точно декомпозирует систему»
а что эта фраза значит, я вообще не понял… поясните.
Ну, это самые основы ООП, сошлюсь на Буча (читали? прошу ответить обязательно, да или нет?). Чтобы более эффективно ответить, вначале спрошу. Что собой представляет класс в ООП? Что он должен представлять (путем кодирования в нем)?
буча читал.
класс — это тип объекта.
есть понятие инкапсюляции. мега важное, оно определяет объект, как сущность определенного единого поведения. Иногда для поведения нужны ещё и данные, но не обязательно.
не нарушается. в мосте у каждой иерархии своя ответственность.
Да? Можете сформулировать, на примере «Машина»-«ФизикаМашины»?
В смысле сформулировать какая ответственность у «Машина»-«ФизикаМашины», и чем они отличаются?
и так задача:
имеется предметная область.
машина, марки машины, цвета, габариты, другие не важные характеристики, двигатели, мощность двигателей, разные мат. модели описывающие движение машины (физика)

Надо всё это описать, отрендерить красиво и тому подобное…

проектируем
1 вариант
заводим базовый класс машина, по матрешке нагручиваем наследование, куда кладем конкретные реализации.
Если классов мало, то вроде как так можно делать, но количество разных комбинаций растет лавинообразно. С этим нужно что-то делать!

Для машин так же может быть какая-то иерархия, которая будет мешаться нашей. Это вторая проблема.

2 вариант
вводим класс машина, а в него кидаем кучу подобъектов:
характеристики марки, физика и тому подобное
Фактически мы активно использовали делегирование
У характеристик создаем параллельные иерархии. Получаем фактически патерн стратегия.

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

3 вариант
часть характеристик, необходимых для управления, живут в объекте машина.
часть характеристик, необходимых для реализации, живут в объекте реализация машины.
получается мост в примитиве. А если для машин сделаем свои иерархии, то получится прямо настоящий мост.

какая ответственность машины? принять управляющие воздействия, отработать бизнес-логику по обработке управляющих воздействий, изменить их и передать реализации.
то есть машина хранит в себе общий поведенческий фактор. это крупная абстракция.

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

В программе удобно обращаться к абстракциям высокого уровня, не задумываясь при этом об абстракциях низкого уровня.

вроде как ничего не упустил.
Насколько я понял, в статье активно предлагаются вариант 1 и вариант 2.

Для простых систем так и надо делать, не нужно усложнять себе жизнь, тяжело поддерживать сразу несколько иерархий, особенно там, где они особо не нужны.

Применяя партены, главное не увлекаться. Если решено сделать несколько классов, вместо одного, то это должно быть сделано ради чего-то, а не для того, чтобы просто применить партен
Да, действительно статья предлагает в основном если не хватает варианта №1, перейти к варианту №2 и успокоится.

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

Попробую найти решение кроме варианта №3. Но уже будет хорошо если читатель поймет, что только в этом случае надо переходить ВРЕМЕННО к варианту №3. (почему временно надеюсь понятно — при развитии системы не крупное становится крупным).

А вот до этого часа Х, не стоит использовать паттерн «Мост»
чем вам не нравится вариант 3?

Я готовлю развернутую статью. Там будет ответ точнее. Если коротко — то это неправильная декомпозиция объектов, искусственное их выделение так как не надо, создавая много жестких связей. Части, которые можно использовать в варианте 2 повторно в варианте 3 перемешаны в реализации. Так же технически если придется убрать мост, надо затратить много тело движений, поэтому лучше проектировать сразу правильно.

Поправка вариант 2 предполагает использование компбинации разных паттернов Адаптер/Фасад/Стратегия/Посетитель/Наблюдатель — в зависимости от ситуации — и все о Мосте можно забыть как о страшном сне.
Имхо как раз наоборот, мост используется чтобы его не нарушать.
Я согласен с некоторыми идеями автора поста, и он прав том, что чем проще код и чем меньше слоев — тем с ним легче работать. А мост вносит как раз новый слой и код, как правило, не упрощает.

Однако я совершенно не согласен с тезисом, что надо в классе делать кучу приватных членов вместо того, чтобы сделать 1 приватный член, ссылающийся на реализацию (как в мосте). И по одной очень простой причине: юнит-тесты.

Автор, у вас был орыт работы в стиле tdd? Ну или хотя бы опыт написания тестов одновременно с кодом, пусть даже не перед ним?

Просто приватные члены — это враг юнит-тестов и tdd (по крайней мере, по моему опыту). Когда приватных членов много (и даже когда много protected, но с ними все же полегче), тесты превращаются в неустойчивую и ненадежную кашу. Поэтому с точки зрения tdd гораздо проще сделать 3 маленьких класса, которые отдельно покрыты тестами (лучше — сразу разрабатывались с ними) и потом из них, как из конструктоа, собрать четвертый (с применением di), чем городить большой класс с кучей приватных членов. Пусть даже эти три мелких класса и будут реализовывать совсем мелкую и второстепенную абстракцию, все равно.

Почему еще хорошо так делать: потому что приватность бывает разной «глубины», что ли. Если в классе 10 приватных членов, часто бывает, что одни из них — более выслкоуровневые, а другие — менее. В итоге при мтдификации класса приходится писать к ним комментарии в стиле «вот это свойство не трожь напрямую, читай только через специальный приватрый же метод, оно низкоуровневое, а вот это — можешь». Когда же применяется агрегация, этой проблемы нет, там сразу видно, что можно торгать, а что — нет.
чем проще код и чем меньше слоев — тем с ним легче работать. А мост вносит как раз новый слой и код, как правило, не упрощает.
Имхо, ровно наоборот (без перегибов). Чем больше решение задачи разделено слои, тем проще вносить изменения в решение, когда задача меняется или в решении найдены ошибки. Проще локализовать место внесения изменений в решение.
Мост не упрощает код. Он только упрощает внесение модификаций.
Неизбежная плата за гибкость.
Спасибо, Mavim, png, DmitryKoterov — за начало серьезного обсуждения. Не хочу, скоропалительно отвечать, надо обдумать ваши аргументы. Но уже с появлением ваших комментариев, я считаю, статья нашла своего читателя. И Вы о чем-то заставили меня подумать. Не то что мне нечем крыть, но ни хочу показаться поверхностным. Кроме того, вы во многом правы. Но есть нюансы. «Будем решать… »
В примере из книги implementor представляет из себя абстракцию, предоставляющую сразу несколько стратегий. Потому меня удивило, что в описании моста ничего не сказано про стратегию.

Мне тоже не очень понравилась формулировка «отделение реализации от абстракции», новичка она может запутать. Поскольку читаю на русском, грешу на перевод.
Sign up to leave a comment.

Articles