Здравствуйте, VladD2, Вы писали:
VD>Хотелось бы услышать кто что думает по поводу необходимости подобных вещей в языках программирования.
VD>Особо привествуются примеры в которых данные фичи дают резкое упрощение решаемой задачи.
Последние примеры из жизни (очень маленькие, но всё же):
Пример #1
У нас в системе есть базовый класс исключения, возникающего при нарушении бизнес-правила — пусть это будет BizRuleViolationException. Где-то в UI стоит обработчик, который ловит все исключения, унаследованные от данного.
Мы также используем NHibernate, у которого есть исключение с забавным названием ValidationFailure. Мы в своих объектах обязаны кидать исключение именно такого (или производного) типа, если известно, что объект невалиден перед операцией сохранения в БД (интерфейс IValidatable).
Наше исключение, производное от ValidationFailure, содержит список поломанных правил и прочую полезную информацию. В то же время у нас есть механизм, преобразующий NHibernate-specific исключения в наши собственные, унаследованные от BizRuleViolationException (так всем проще). Как итог у нас есть два исключения-близнеца, унаследованные от разных базовых исключений, но содержащие один и тот же список поломанных правил и т.д. Тут бы я от traits или чего-то такого не отказался бы.
Пример #2
Во всё том же проекте есть бизнес-объекты и коллекции. И есть необходимость запускать некий код перед/после операций с базами данных. Как результат, базовый класс бизнес-объекта и базовый класс коллекции имеют набор методов вроде OnBeforeInsert/OnAfterInsert/... и соответствующих свойств. А один базовый класс тут невозможен потому, что коллекция, ясен пень, унаследована от List<T>.
Здравствуйте, VladD2, Вы писали:
VD>Хотелось бы услышать кто что думает по поводу необходимости подобных вещей в языках программирования. VD>Особо привествуются примеры в которых данные фичи дают резкое упрощение решаемой задачи.
Да было уже. Обсуждалось. Хочется освежить память?
(От себя замечу: техника mixins лучше множественного наследования.)
Здравствуйте, VladD2, Вы писали:
VD>Хотелось бы услышать кто что думает по поводу необходимости подобных вещей в языках программирования.
VD>Особо привествуются примеры в которых данные фичи дают резкое упрощение решаемой задачи.
Множественное наследование незаменимая вещь, юзаем по полной программе.
Реальный пример:
class QS_Face
: public QS_Object // базовый стафф
, public ListNode2<QS_Face,QS_Shell> // узел для интрузивного списка в классе Shell
, public ListNode2<QS_Face,QS_Model> // узел для интрузивного списка в классе Model
, public SCX_Member // этот класс позволяет обьекту отображатся на 3д сцене, аггрегация не подходит т.к. перегружаются виртуальные функции
{
public:
// понеслась
оказалось, что в Unit-тестах, инициирующих компиляцию C++ кода дублируется слишком много одинакового кода. Поэтому сначала я захотел вынести этот код в отдельный базовый класс для Unit-тестов проверяющих компиляцию:
class TestWithCompilation < Test::Unit::TestCase
... # общий для всех код.end# Конкретный тест.class TC_RuCodeGen < TestWithCompilation
... # реализация конкретного теста.end
Но здесь обнаружились грабли: фреймоворк воспринимал всех наследников от TestCase как конкретные тесты и пытался их запускать. При попытке запуска TestWithCompilation ни одного test-метода не находилось и TestCase генерировал исключение. Чтобы обойти эту шнягу пришлось оформить TestWithCompilation в виде mixin-а и подмешивать его в конкретные классы тестов:
class TC_RuCodeGen < Test::Unit::TestCase
include TestWithCompilation
... # реализация конкретного теста.end
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Andrei N.Sobchuck, Вы писали:
ANS>Блин, Влад, ты прикалуешся? Никогда никогда наследование не использовал?
Я не прикалываюсь. Мне интересны разные мнения.
Раньше я часто пользовался МН так как в ATL-е это был принятый паттерн наширения. Но перейдя на C# я со временем понял, что нужды в МН не испытываю. Возможно задачи такие были. Вот и интересны другие примеры.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Kluev, Вы писали:
K>Множественное наследование незаменимая вещь, юзаем по полной программе. K>Реальный пример: K>
K>class QS_Face
K> : public QS_Object // базовый стафф
K> , public ListNode2<QS_Face,QS_Shell> // узел для интрузивного списка в классе Shell
K> , public ListNode2<QS_Face,QS_Model> // узел для интрузивного списка в классе Model
K> , public SCX_Member // этот класс позволяет обьекту отображатся на 3д сцене, аггрегация не подходит т.к. перегружаются виртуальные функции
K>{
K>public:
K> // понеслась
K>
Вы и запятые кверху задом юзайте. Это мало интересно. Интересны именно ситуации когда это дает приемущество перед другими подходами. То есть кокретные примеры и обоснования.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Ну, наконец-то один ответ по теме! Уже за это хочется поставить оценку!
E>Например, иногда требовалось сделать объект одновременно Qt виджетом и SObjectizer
E>class GEMONT_1_QT_WIDGET_TYPE a_qt_widget_t
E> : public QWidget
E> , public so_4::rt::agent_t
E>{
E> Q_OBJECT
E> //! Псевдоним базового типа.
E> typedef so_4::rt::agent_t so_base_type_t;
E>...
E>
Это по мне так пример плохого дизайна. Смешивание в однгом объекте бизнес-логики и презентационной логики.
E>...Но здесь обнаружились грабли: фреймоворк воспринимал всех наследников от TestCase как конкретные тесты и пытался их запускать...
То есть на лицо неграмотно спроектированный фрэймворк? В NUnit и MS-ых юнит-тестах подобных проблем нет, и МН не требуется.
Получается, что оба примера — поимеры неверного проектирования других ошибок. Вот и я к тому же прихожу анализируя свой опыт.
Вот и встает вопрос, а так ли действительно нжуно МН или его заменители?
Хотелось бы найти пример в коотором МН или его заменители давли бы действительно ощутимое приемущество и сама задача при этом не была бы вызвана ошибочным дизайном.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Это по мне так пример плохого дизайна. Смешивание в однгом объекте бизнес-логики и презентационной логики.
E>>...Но здесь обнаружились грабли: фреймоворк воспринимал всех наследников от TestCase как конкретные тесты и пытался их запускать...
VD>То есть на лицо неграмотно спроектированный фрэймворк? В NUnit и MS-ых юнит-тестах подобных проблем нет, и МН не требуется.
VD>Получается, что оба примера — поимеры неверного проектирования других ошибок. Вот и я к тому же прихожу анализируя свой опыт.
VD>Вот и встает вопрос, а так ли действительно нжуно МН или его заменители?
Судя по твоим ответам, ты специально задал этот вопрос, чтобы отчитать тех, кто на него ответит по существу, за плохой дизайн.
Мне кажется, что у тебя не возникало желания использовать МН не потому, что оно помогло бы решить задачу проще и лучше, а потому что ты "думал на С#" и, соответсвенно, подсознительно исключал для себя возможность использования МН. Те, кто "думает на C++", видят эти возможности чаще и не считают это признаком плохого дизайна.
Здравствуйте, VladD2, Вы писали:
VD>Это по мне так пример плохого дизайна. Смешивание в однгом объекте бизнес-логики и презентационной логики.
Вообще-то ноги такого наследования растут из того, что в том же Qt (насколько я знаю и в wxWidgets, и в FOX) обращаться к GUI элементам (окнам, контролам) или рисовать на экране можно было только из главной нити приложения. Соответственно, задачка on-line отображения на экране информации, возникающей в недрах SObjectizer была не очень тривиальной. А это было приложение, которое в real-time (мягком) отображало мониторинговую информацию. Нужно было как-то организовывать взаимодействие между View-виджетом и работающими агентами. Что влекло за собой какую-то очередь (возможно не одну), синхронизацию, уведомления и пр. В то время как SObjectizer все это уже предоставлял в готовом виде. Так что в смешение QWidget и агента не было смешением бизнес-логики и презентации. Скорее это был способ создания презентации, способной в real-time воспринимать информацию от бизнес-логики. Гораздо более простой и дешевый способ, чем использование вспомогательных подпорок в виде дополнительных очередей сообщений.
VD>Получается, что оба примера — поимеры неверного проектирования других ошибок. Вот и я к тому же прихожу анализируя свой опыт. VD>Вот и встает вопрос, а так ли действительно нжуно МН или его заменители?
Если мне не отшибает память, то Гради Буч о МН отзывался так:
Множественное наследование -- это запасной парашют. В большинстве случаев в нем нет необходимости, но как же здорово, когда в один прекрасный момент он оказывается под рукой.
(за точность цитаты не ручаюсь, нет книги под рукой).
Исходя из мнения Буча как раз можно ожидать, что МН будет использовано для обхода чьих-то (в большинстве случаев, однако, своих ) кривых решений.
Вот еще один пример (что называется из не написанного). У меня в Ruby есть класс Toolset, от которого должны создаваться наследники для адаптации к конкретным компиляторам (VcFamily, GccFamily, C89Family и пр.). Все было хорошо и складно до тех пор, пока не оказалось, что для Unix-платформ нужно поддержать специфическую функциональность: жесткое задание линкеру типа библиотеки (в Unix аргумент -lA может означать как линковку libA.a (статической библиотеки), так и libA.so (динамической библиотеки), а нужно было, чтобы -lA означал линковку строго libA.a). Соответственно, в toolset-ах, которые работают под Unix-ом появляется набор одинаковых методов и атрибутов, которые хорошо было бы вынести в общий класс. Могло бы показаться, что достаточно сделать от Toolset наследника UnixToolset, чтобы от UnixToolset уже наследовать классы для Unix-овых компиляторов. Однако, не все так просто. Для того же самого GCC есть порты под Windows (MinGW и в Cygwin). И как раз иерархию классов GccFamily разбавить UnixToolset-ом уже не просто. В этом случае проще выделить данную функциональность в какой-нибудь mixin UnixLibLinking и подмешивать его к Unix-овым toolset-ам, унаследованным от Toolset или каких-то родовых классов (вроде GccFamily или C89Family).
Так что и этот пример может быть свидетельством преодоления кривизны. Только уже кривизны окружающей дествительности, на которую я не имею возможности повлиять
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, dshe, Вы писали:
D>Судя по твоим ответам, ты специально задал этот вопрос, чтобы отчитать тех, кто на него ответит по существу, за плохой дизайн.
Отнюдь. Плевать мне на чужой дизайн. Меня интересует аргументация.
D>Мне кажется, что у тебя не возникало желания использовать МН не потому, что оно помогло бы решить задачу проще и лучше, а потому что ты "думал на С#" и, соответсвенно, подсознительно исключал для себя возможность использования МН.
Я думаю по-русски. И ничего не исключаю. Понято, что когда я пишу на C#, то отметаю эту возможность. Но при этом я испытываю, или не испытываю дискомфорт дискомфорт от этого. Вот я и заметил, что в общем-то не могут припомнить ни одного случая дискомфорта.
D> Те, кто "думает на C++", видят эти возможности чаще и не считают это признаком плохого дизайна.
Я писал на С++ не мало и не мало использовал то, без чего потом обходился без малейшего сожаления.
Вопрос же этот я поднял потму, что не раз слышал (да и сам говорил) о необходимости введения в C# миксинов или трэйтсов. Вот мне и интересно не является ли это стремлением к увеличению фич без особой на то необходимости.
Дело в том, что вчера мне потребовалось обосновать необходимость traits в Nemerle: http://nemerle.org/forum/viewtopic.php?t=244&sid=533a4646e8a58584c5dee21a456b355e
и я задумался над аргументацией. Задумался, и понял, что ее в общем то у меня пока нет. Вот и решил получить помощь. Однако пока что никто ничего умного не сказал.
Вот я и думаю, не является ли МН и его заменители эдакой показной фичей которая реально на фиг не упала, но так как в языке Х она есть, то и нам надо заиметь?
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Если подитожить, то получается что МН и т.п. незаменимы когда нужно обходить проблемы вызванные некорректным дизайном. И если свой дизайн можно изменить, то в случае с чужим дизайном мы имеем депйствительно трудно разрешимую пролему которая относительно хорошо решается подключением готовой реализации к имеющемуся классу. Так?
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Если подитожить, то получается что МН и т.п. незаменимы когда нужно обходить проблемы вызванные некорректным дизайном. И если свой дизайн можно изменить, то в случае с чужим дизайном мы имеем депйствительно трудно разрешимую пролему которая относительно хорошо решается подключением готовой реализации к имеющемуся классу. Так?
Да, если рассматривать МН как способ преодоления проблем, то именно так и получается.
Но мне еще всгда казалось, что МН может быть хорошим приемом проектирования при необходимости объединения разнородной функциональности в одном месте. Для меня самым показательным примером является такая сущность, как "мнемосхема" в АСУТП: эта такая картинка с изображением технологического процесса, на которой в динамике отображаются показатели датчиков, состояние оборудования, какие-то наиболее важные сообщения и т.д. Т.е., с одной стороны -- это GUI объект (какой-нибудь widget, производный от соответствующего базового класса), с другой стороны -- это какой-то слушатель (наблюдатель). Здесь гораздо проще унаследоваться одновременно от нескольких базовых классов, чем использовать множественное наследование интерфейсов, а затем через агрегирование встраивать всю функциональность.
Т.е. МН может еще и служить средством борьбы с изначальной сложностью проблемы и/или жесткостью органичений на возможные решения.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, VladD2, Вы писали:
VD>Здравствуйте, Kluev, Вы писали:
K>>Множественное наследование незаменимая вещь, юзаем по полной программе. K>>Реальный пример: K>>
K>>class QS_Face
K>> : public QS_Object // базовый стафф
K>> , public ListNode2<QS_Face,QS_Shell> // узел для интрузивного списка в классе Shell
K>> , public ListNode2<QS_Face,QS_Model> // узел для интрузивного списка в классе Model
K>> , public SCX_Member // этот класс позволяет обьекту отображатся на 3д сцене, аггрегация не подходит т.к. перегружаются виртуальные функции
K>>{
K>>public:
K>> // понеслась
K>>
VD>Вы и запятые кверху задом юзайте. Это мало интересно. Интересны именно ситуации когда это дает приемущество перед другими подходами. То есть кокретные примеры и обоснования.
А это пример значит неконкретный?
Пример вполне тривиальный и жизненный. Грубо говоря обьект разделяется м-ду двумя документами. Один — модель, владелец всех обьектов, через которую идет вся работа с данными, другой документ 3-д сцены, который юзается для отрисовки и UI манипуляций. т.е. не классичесский doc — view, а doc — proxy_doc — view. Где doc — модель, proxy_doc — сцена3д, view — окно_сцены. Преимущества такого подхода очевидны. Модель — крайне ветвиста и наворочена, в то время как на сцене может отображатся фактически только три примитива точка, линия и поверхность из треугольников. Взаимодействие с юзером, а именно просмотр и выбор обьектов мышкой идет через окно сцены. Если бы вид одевался бы непосредственно на модель, то это было бы крайне неэффективно т.к. пришлось бы работать с кучей разных типов, которые к тому же быстро эволюционируют в процессе разработки. Выход создание промежуточного документа, и множественное наследование используется чтобы сделать обьект участником двух документов одновременно: QS_Object — обязательный класс для всех обьектов модели, а если я хочу его бросить на сцену, то дополнительно юзаю SCX_Member. Без множ. наследования пришлось бы делать SCX_Member частью обьекта, а вместо виртуальных функций делать делегаты. Кривизна на лицо. Делать SCX_Member интерфейсом — тоже непрокатит, т.к. в нем немалый кусок реализации. Грубо говоря работа идет так: Делаем класс модели участником сцены (наследуемся от SCX_Member) далее через в процессе работы через SCX_Member добавляем на сцену конкретные обьекты (кривые, точки, поверхности). Когда данные обьекта модели становятся невалидными в SCX_Member ставится флаг dirty. Когда закончены все back-end операции и пришло время рисовать, для нужных обьектов (dirty==true) вызывается SCX_Member::regen (пересоздаем представление обьекта).
Это простой и естественный пример когда мн-наследование уместно и без него имеем грабли. Остальные подходы (типа аггрегации и делегирования) работают, но не так просто и красиво как мн-наследование.
K>Пример вполне тривиальный и жизненный. Грубо говоря обьект разделяется м-ду двумя документами. Один — модель, владелец всех обьектов, через которую идет вся работа с данными, другой документ 3-д сцены, который юзается для отрисовки и UI манипуляций. т.е. не классичесский doc — view, а doc — proxy_doc — view. Где doc — модель, proxy_doc — сцена3д, view — окно_сцены. Преимущества такого подхода очевидны. Модель — крайне ветвиста и наворочена, в то время как на сцене может отображатся фактически только три примитива точка, линия и поверхность из треугольников. Взаимодействие с юзером, а именно просмотр и выбор обьектов мышкой идет через окно сцены. Если бы вид одевался бы непосредственно на модель, то это было бы крайне неэффективно т.к. пришлось бы работать с кучей разных типов, которые к тому же быстро эволюционируют в процессе разработки. Выход создание промежуточного документа, и множественное наследование используется чтобы сделать обьект участником двух документов одновременно: QS_Object — обязательный класс для всех обьектов модели, а если я хочу его бросить на сцену, то дополнительно юзаю SCX_Member. Без множ. наследования пришлось бы делать SCX_Member частью обьекта, а вместо виртуальных функций делать делегаты. Кривизна на лицо. Делать SCX_Member интерфейсом — тоже непрокатит, т.к. в нем немалый кусок реализации. Грубо говоря работа идет так: Делаем класс модели участником сцены (наследуемся от SCX_Member) далее через в процессе работы через SCX_Member добавляем на сцену конкретные обьекты (кривые, точки, поверхности). Когда данные обьекта модели становятся невалидными в SCX_Member ставится флаг dirty. Когда закончены все back-end операции и пришло время рисовать, для нужных обьектов (dirty==true) вызывается SCX_Member::regen (пересоздаем представление обьекта). K>Это простой и естественный пример когда мн-наследование уместно и без него имеем грабли. Остальные подходы (типа аггрегации и делегирования) работают, но не так просто и красиво как мн-наследование.
У меня похожий образец UI в Java, но я обошёлся агрегированием вместо множественного наследования.
Есть view — канва, на которой отображаются виджеты.
Есть proxy_doc — синглетон-контейнер для однотипнах объектов-моделей — doc'ов в вашей терминологии.
proxy_doc ничего не известно о конкретной моделе, но известно об области канвы; он может только правильно отрисовать модель, нуждающуюся в отрисовке (реконструировать dirty_doc, заодно "превратив" их обратно в doc ).
Основной момент в таком образце: резко сокращаются накладные расходы на перерисовку всей сцены. Затрагивается только то, что действительно нуждается в обновлении. "Лишние" требования моделей на перерисовку себя отсекаются в proxy_doc и не приходят к view.
Здравствуйте, VladD2, Вы писали:
VD>Хотелось бы услышать кто что думает по поводу необходимости подобных вещей в языках программирования. VD>Особо привествуются примеры в которых данные фичи дают резкое упрощение решаемой задачи.
Однажды я делал свою реализацию контролов которая может отображаться и через WinForms и через WebForms и через черт знает что еще. Иерархия контролов состояла только из их интерфейсов. Реализация для каждой платформы была своя.
Так вот при реализации отображения дли WinForms нужно было реализовать кучу своейст и методов этих интерфейсов. Причем кроме конкретных интерфейсов были еще и общие интерфейсы для разных контролов так вот их реализация была идентична. Дело было на C#... Короче словил я на этой задаче кучу копипаста.
... << RSDN@Home 1.1.4 beta 6a rev. 436>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн