LINUX.ORG.RU

Как жить без множественного наследования?


0

0

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

Вот уткнулся сейчас в проблему вообще в банальном PHP...

Задача - есть набор классов (с большим набором методов) тех или иных web/db-ресурсов. Это может быть простой ORM-маппинг, может быть дерево, хитро извлекаемое из БД, список...

Их наследники - конкретные типы объектов. Грубо говоря - простая страничка сайта, или вывод сложной формы...

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

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

Пока нам хватает стандартных настроек - всё прекрасно.

Но вот нам для _раздела сайта_ требуется ряд общих настроек. Например, идентификаторы баннеров, имя шаблона...

Если все виды страниц относятся к одному классу - опять прекрасно. Делаем промежуточный класс, где всё это прописываем, а уже от него наследуем наши страницы.

Но что делать, когда страницы должны принадлежать разным базовым классам??

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

А вот без него - фиг.

Каким боком тут можно прикрутить те же интерфейсы, скажем - просто не представляю.

Как быть-то?

★★★★★

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

krum
()
Ответ на: комментарий от krum

Ну и, конечно же, банду четырйх освоить вдоль и поперёк.

krum
()
Ответ на: комментарий от krum

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

И вместо одиночного тоже. Наследование -- зло! Интересно, когда же, наконец, уберут наследование из ООП?

watashiwa_daredeska ★★★★
()
Ответ на: комментарий от krum

Да, это постоянно утверждаемая истина, но я не вижу способа эффективно применить композицию в моём случае.

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от watashiwa_daredeska

>И вместо одиночного тоже. Наследование -- зло! Интересно, когда же, наконец, уберут наследование из ООП? Блин. Ну вот практическая задача. Есть: String title(); Page[] parents(); int modify_time(); .... и ещё десятка два атрибутных методов. Мне нужно создать класс, в котором будет, скажем, реализован метод onAction(Hash data). В случае наследования - я добавляю только один метод. Всё. В случае композиции мне придётся делать обёртки для всех двух десятков методов. Бред.

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от KRoN73

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

jek_
()
Ответ на: комментарий от jek_

Нет. В том-то и дело, что задача стоит в очень небольших (один метод - одна строка, число методов - несколько штук) расширениях крупных базовых классов.

Т.е. вот прямо сейчас проблема такая.

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

Но у этих объектов должно быть несколько общих методов.

При множественном наследовании я бы описал небольший класс с этими свойствами (имя шаблона для вывода, набор параметров шаблона, время кеширования и т.п.), общими для всех классов этой категории и сделал бы его вторым родителем.

Сейчас же на выбор остаются:

- Делать копи-паст методов с этими параметрами в каждый класс (их немного, но это некрасиво и породит проблемы в будущем при их модификации - потребуется массовое редактирование)

- Делать у каждого объекта поле этого нового класса (эдакая композиция), но тогда потребуется инициализировать этот класс + дёргать его из иных методов + потеря производительности (некоторые из параметров дёргаются часто).

Или я упускаю иной вариант?

KRoN73 ★★★★★
() автор топика

Возвращаясь к исходной теме. Главный (и единственный?) аргумент против множественного наследования - неоднозначности в случае появления одинаковых методов у разных родителей.

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

Так откуда тогда могут взяться неоднозначности?

Мне, всё же кажется, что тут у народа какой-то массовый заскок.

Объектное порграммирование тем и полезно, что хорошо вписывается в объекты окружающего мира.

Но, извините, "множественное наследование" в окружающем мире как раз явление массовое и постоянное.

Су-33 относится и к авиации и к флоту.

Яблоко - это растение, объект зелёного цвета, пища.

Звезда - это астрономический объект, природный термоядерный реактор, центр планетарной системы...

И ООП, которое изначально сделало шаг в этом направлении, потом за совершенно неадекватными оговорками начинает вводить костыли в виде интерфейсов, композиций и т.п....

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от watashiwa_daredeska

Интересно, когда же, наконец, уберут ООП из программирования?

Miguel ★★★★★
()
Ответ на: комментарий от KRoN73

> В случае наследования - я добавляю только один метод. Всё. В случае композиции мне придётся делать обёртки для всех двух десятков методов. Бред.

1. Мой пост был намеренно абсурдным, чтобы показать неправильность той точки зрения, что множественное наследование нужно заменять композицией.

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

watashiwa_daredeska ★★★★
()
Ответ на: комментарий от jek_

> геморроя с одноимёнными методами и ромбовидными структурами будет на порядок больше, чем при использовании композиции.

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

watashiwa_daredeska ★★★★
()
Ответ на: комментарий от KRoN73

>Объектное порграммирование тем и полезно, что хорошо вписывается в объекты окружающего мира.

это миф такой

ты наливаешь в чайник воду, но при этом тебе никак не упросить чайник налиться водой. OOP - it's all about data. это просто такой удобный способ оперировать данными. в реальной жизни это не так.

А пример про яблоки и звёзды очень уж надуманный. интересно, банка зелёнки и яблоко, являясь "объектами зелёного цвета" чего общего будут иметь? getColor() == GreenObject.GREEN ? а setColor будет на тот случай если яблоко покраснеет? а с зелёнкой что делать? бардак? втопку.

Pi ★★★★★
()
Ответ на: комментарий от KRoN73

> Вот уткнулся сейчас в проблему вообще в банальном PHP...

> Но любой, даже самый тупой компилятор должен уметь отлавливать эти неодноначности на этапе компиляции.

PHP -- не компилятор.

> Так откуда тогда могут взяться неоднозначности?

Вот оттуда и берутся. На самом деле, они есть даже у компилируемых языков, но в них есть средства disambiguation, решающие эти проблемы. См., например, CLOS.

watashiwa_daredeska ★★★★
()
Ответ на: комментарий от Pi

>А пример про яблоки и звёзды очень уж надуманный.

Хорошо, вот практическая задача, на которой я заткнулся уже более детально:

Есть базовый класс "страница сайта", содержащий массу методов, возвращающих параметры страницы (из которых система потом и собирает эту страницу):

title() - заголовок страницы

description() - её описание

source() - текст

body() - скомпилированный (разметка -> html) текст, по умолчанию сам собирается из source()

create_time() - дата создания

modify_time() - дата изменения

и т.д. и т.п.

У базовой страницы методы в основном абстрактные, но у неё куча наследников. Страницы, которые извлекают данные из файлов файловой системы, страницы, которые извлекают данные и базы данныех и т.п.

Извлечение из БД может идти тоже разными способами, это могут быть простые плоские данные, когда текст страницы берётся из некой таблицы, скажем, текст постинга на форуме по его ID. Может быть построение дерева из данных дерева категорий, заданного в виде графа, могут быть списки и т.п.

Но у сайта есть разделы. Совершенно разнородные страницы могут быть объединены в одном разделе. Список форумов, скажем, чисто динамическая штука, также - список топиков в форуме. А вот уже постинги внутри форума - это совсем иной механизм (на самом деле раздел состоит из ещё более разнородных разделов, как раз форумы я сумел свести к единому базовому классу, тут это проблемы нет уже, но как пример). Но у этих разнородных наследников разныз рлдителей есть масса общих свойств. В первую очередь - шаблоны и их параметры. Имена секций сайта, наборы ссылок, меню и т.п.

В случае множественного наследования проблем нет. Скажем, наш список наследует классы def_dbtree и my_section и в нём пишется, подчас, буквально несколько строк кода. Запрос, генерирующий данные для дерева (само дерево соберётся методами из def_dbtree, которые будут делать этот запрос), небольшая подстройка данных по умолчанию в my_section (скажем, у всех страниц единый ID баннера в баннерной сети, но для отдельного класса страниц он должен быть иной).

Но у нас множественного наследования нет.

Что можно предложить взамен?

Композиция отпадает сразу. Потребуются обёртки для многих десятков параметров.

Копипаст?

Костыли, одни костыли...

Задачу я решил таким костылём: Загрузчик классов проверяет наличие в том же каталоге, откуда грузится нужный класс файла другого класса с зарезервированным именем и при его наличии - грузит и инициализирует, передавая в качестве параметра только созданный наш объект. Поскольку (повезло) все общие параметры конечных классов в этом частном случае можно задать через set-методы, то этот вторичный "класс-инициализатор" этим занимается. А все классы данной категории сайта лежат в одном каталоге, так что и инициатор для них - тоже один.

К сожалению, это очень частный случай.

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от watashiwa_daredeska

>PHP -- не компилятор.

Это роли не играет. Менее глючным от отсутствия множественного наследования он не становится :)

...

На самом деле - компилятор (в байткод). И как раз _такую_ ошибку он отловить может. После выкидывания агрегаторов методов в PHP5 он на этапе компиляции знает _все_ методы класса. И легко мог бы поймать ошибку дублирования методов.

А "компилятор" взялся - из-за того, что я рассматриваю проблему не только частную, в PHP. Сколько раз приходилось вводить костыли в ту же Java.

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от KRoN73

> не хотите же вы сказать, что реализация множественных интерфейсов -- зло? Ведь интерфейс -- суть pure abstract class, следовательно геморрой тот же.

Нет, я имел ввиду именно ромбовидные структуры с одноимёнными методами.

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

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

jek_
()
Ответ на: комментарий от jek_

>но научить компилятор запрещать создание ромбиков

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

jek_
()
Ответ на: комментарий от jek_

> если разрешить множественное наследование, но научить компилятор запрещать создание ромбиков, то вся идеология ООП накроется медным тазом.

Не надо запрещать. Надо:

1. Предоставить средства разрешения неоднозначностей.

2. Сместить акцент с собственно классов/интерфейсов на сообщения (методы). В том же CLOS, я вызываю метод method1 и мне пофиг какие предки у объекта. В CLOS метод вообще не является частью декларации класса.

watashiwa_daredeska ★★★★
()
Ответ на: комментарий от watashiwa_daredeska

>Предоставить средства разрешения неоднозначностей.

Да, в С++ они есть. Но ведь от этого не легче - множественное наследование требует КРАЙНЕ осторожного обращения.

>Сместить акцент с собственно классов/интерфейсов на сообщения (методы).

Да, это один из выходов. Но ведь пока доминируют ООП языки, где ключевая сущность - класс.

jek_
()
Ответ на: комментарий от jek_

>множественное наследование требует КРАЙНЕ осторожного обращения.

А банальный композинг требует ещё более осторожного обращения.
Когда при написании класса вместо добавления одно-двух методов
нужно описать две десятка обвязок, приходится писать в 10 раз больше
кода, при этом в 10 раз возрастает риск пропустить ошибку. Ежу
понятно, что любой типичный программист начинает делать копипаст.

И вместо:

int modify_time() { return parent2->modify_time(); }
int create_time() { return parent2->create_time(); }

мы получим:

int modify_time() { return parent2->modify_time(); }
int create_time() { return parent2->modify_time(); }

Ошибку такого рода отлавливать будем _очень_ долго. Я сам на эти
грабли (в немного более сложных случаях, естественно, но таких же по
принципу) наступал несколько раз.

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

Ну и нафига, спрашивается, выкидывается множественное наследование?

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от jek_

>А если разрешить множественное наследование, но научить компилятор запрещать создание ромбиков

А зачем запрещать-то? Если у предков крайнего класса базовый метод не переопределялся - то и неоднозначности нет. Если переопределялся - то с точки зрения компилятора это будут уже _разные_ методы с одним именем, так что он в случае неоднозначности просто выдаст ошибку и потребует уточнения.

KRoN73 ★★★★★
() автор топика
Ответ на: комментарий от KRoN73

>> Ведь интерфейс -- суть pure abstract class, следовательно геморрой тот же.

> Да, именно так.

ЭЭх, Алан Кей бы пристрелил вас обоих.

Интерфейс -- суть набор методов, который должен поддерижвать обьект, а абстрактный класс - это костыль, для языков вроде C++, что-бы реализовывать полиморфизм. При этом, надо сказать, что абстрактные классы это не единственный способ добится подобного результата (советую почитать что-нибудь про тот-же Objective-C, или к истокам возвращаясь, Smalltalk).

fmj
()

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

Обезьяны не осиливают.

anonymous
()

> Вот уткнулся сейчас в проблему вообще в банальном PHP...

Твоя проблема решается очень легко :). ПХП --- дохлячок и решить эту задачу в его рамках без дублирования не получится. Поэтому тебе нужен мета-уровень.

Описываешь в некой структуре: XML, yaml или просто ассоциативном массиве(выбери по вкусу) в декларативном стиле, что тебе нужно от данной странички и какой функционал.

Теперь из созданной структуры нужно сделать ПоХаПэ :). Натравливаешь на нее мини-компилятор, шаблонный движок и т.п. И он тебе генерирует монстро-классы с кучей дублирующихся методов, которые будут "условно множественно пронаследованны".

Но тебя это не парит. Правка идет на мета-уровне.

Теперь меняем слово XML и мини-компилятор на Lisp/Scheme/CLOS(или может даже Forth?), придумываем декларативную структуру, которая будет реализовывать эту задачу. И все. Дальше только нужно будет решить задачу вставки кусков пхп-кода в шаблонные стурктуры, когда задача будет нетиповая.

Cris
()

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

Если говорить о реализации, скажем в C++, то есть сложности с повторяющимся (пересекающимся) множественным наследованием (ключевое слово virtual в списке наследуемых классов). Возникает неоднозначность в интерпретации. В общем, нафиг нужны такие проблемы, если очень многие задачи итак успешно решаются с помощью интерфейсов, которые куда более однозначны (например, в C# решена задача в совпадением имен методов разных интерфейсов), и потому они куда проще реализуются. Говоря математически, С++- реализация множественного наследования не совсем корректна (из-за неоднозначности в сложных случаях).

Помню, что в C# изначально проектировали включить множественное наследование. Видимо, потом хорошо подумав, решили пойти по пути Java и исключили их из релиза, при этом реализовав достаточную гибкую парадигму интерфейсов.

dave ★★★★★
()
Ответ на: комментарий от dave

> Видимо, потом хорошо подумав, решили пойти по пути Java и исключили их из релиза, при этом реализовав достаточную гибкую парадигму интерфейсов.

Вот ты мне объясни, что за бред ты написал? Я вообще непонимаю радостного дрочинга ява и шарп девелоперов на интерфейсы...

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

Скажем в Руби есть миксины. И они решают эту проблему. Общий код описывается в модуле, а потом подключается в класс. Например модуль бежать и прыгать. Два инклуда и все шоколадно.

Но где такая фича в шарпе и яве? Ее нету. Соответственно разрабы явы и шарпа промыли мозги людям. Фичу забрали, и перданули, что вот мы все кльово сделали и теперь не будет такого гиммора как в С++.

Cris
()
Ответ на: комментарий от Cris

Бедный... Какие же нехорошие оказались эти разрабы явы и шарпа! Фичу отобрали... :(

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

Потом мой "бред" касался того, что множественное наследование реализации порождает неоднозначность, тогда как множественное наследование интерфейсов - нет. А это проблема на многие порядки серьезнее проблемы повторного кодинга, если последняя, вообще, имеет место быть, в чем я сильно сомневаюсь ;)

Понимаешь, есть вещи, которые не заметны на приложениях уровня HelloWorld, но которые становятся очень опасной игрушкой в сложных приложениях. Множественное наследование классов - из числа таких опасных игрушек.

dave ★★★★★
()
Ответ на: комментарий от dave

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

Скажу по секрету: реализация по приоритету выше интерфейса. Просто потому, что без интерфейса можно реализовать работающее приложение (что есть высший приоритет), а интерфейс без реализации них... работать не будет.

А всякие уроды, рассуждающие в стиле: "наколбасить лишние n kSLOC -- это "удел тех, кто занимается кодингом", а мне так нравится, потому что код я не пишу" обычно огребают проблемы на свою задницу.

> множественное наследование реализации порождает неоднозначность, тогда как множественное наследование интерфейсов - нет.

В чем разница между конфликтными ситуациями:

1. класс C реализует интерфейсы A, B, в каждом из которых есть метод void f()

2. класс C наследует классам A, B, в каждом из которых есть метод void f()

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

> Множественное наследование классов - из числа таких опасных игрушек.

Правильно. Обезьянам опасных вещей давать нельзя. Убьются еще, да и другим повредить могут. Пусть они лучше kSLOC'и колбасят. В общем, детскому саду -- Lego в руки.

watashiwa_daredeska ★★★★
()
Ответ на: комментарий от watashiwa_daredeska

> Скажу по секрету: реализация по приоритету выше интерфейса.

Учи ООП дитятко, а не "классы в C++". Без интерфейсов не будет ООП, а без наследования реализации не будет работать твоя и только твоя программа (ну еще и таких-же подростков как ты).

>> Множественное наследование классов - из числа таких опасных игрушек.

> Правильно. Обезьянам опасных вещей давать нельзя. Убьются еще, да и другим повредить могут. Пусть они лучше kSLOC'и колбасят. В общем, детскому саду -- Lego в руки.

Так что-же вы пытаетесь им игратся, если сами говорите, что нельзя?

Пишите на голом C, реализуйте все ООП ручками (как например это сделано в коде VFS почти любой UNIX-подобной системы), у вас будут вообще все возможности открыты, -- вот вам куча возможностей, неограниченные возможности, а последствия -- запятую забыл и крах системы.

Нужно четко представлять, для чего какие средства нужны, если сказано "C# он для того, что-бы меньше с кодом ибаццо", так это значит, что там специально убраны потенциально опасные моменты, не потому что "все идиоты", а потом что даже опытные программисты допускают элементарные ошибки, фатальные для кода, и чем меньше возможностей совершить подобную ошибку в том или ином языке, тем язык лучше для общей (не заточенной на какую-то конкретную узкую область) практики.

Если вам нравится C++ - дык, ну пишите на нем Web код (я в детстве так делал, так что не вопить, что это невозможно/сложно, -- все просто), будете иметь в 10 раз больше гемора с отладкой, чем на быдло PHP.

fmj
()

Для затравки,

небольшое  сравнение языков программирования: 

Следующая таблица указывает среднее значение Errors per Function Point 
для некоторых языков (по данным Linea Engineering): 

Smalltalk		0.14	
Visual Basic	0.21	
Ada 95		0.50	
Java			0.50	
C++			0.82	
C			2.50


Данная таблица указывает среднее количество строк кода на единицу функциональности 
(Average Source Statements per Function Point) 
(по данным Software Productivity Research, Inc. (www.spr.com)): 

Smalltalk		21	
Ada 95		49	
Java			53	
C++			53	
COBOL			107	
C			128
	

Меньше кода означает более быстрое написание и легкое сопровождение. 


Видим, что язык C++, весьма далеко отстает от языков с отключенным 
множественным наследованием.

(материал взят с http://www.smalltalk.ru/articles/why.html )

fmj
()
Ответ на: комментарий от watashiwa_daredeska

Я говорил о другой конфликтной ситуации. О ней чуть позже.

Что касается твоего вопроса, то у меня ответный вопрос. Ты знаком с C#? Именно с C#, а не с Java.

1. Если класс C реализует интерфейсы A, B, в каждом из которых есть метод void f(), то в самом общем случае имеем три разных метода f(): A.f(), B.f() и сообственно C.f(). В случае Java мы можем задать только один такой общий метод. В случае C# все три метода могут быть разными. Таким образом разруливаются многие конфликты между интерфейсами.

2. Сам по себе твой пример с классами не интересен. Проблема возникает, если оба класса A и B имеют общего предка в лице класса D с методом f(). Вот тут и начинаются главные сложности. Какую реализацию D.f() будет наследовать C.f()? В случае C++ этот вопрос не имеет однозначного ответа, поскольку есть, как минимум, две разные ситуации: (1) объект D - общий (virtual) для A и B, (2) каждый из объектов A и B несет по своему собственному объекту D. В данном случае объект - это эквивалент реализации. Если бы число классов было больше, то число таких перестановок всех возможных реализаций только бы возрастло. Ну, и зачем нужен такой геморp? Другими словами, если иерархия классов сложная, то есть очень высокая вероятность элеменарно запутаться в этих реализациях и получить алгоритмическую ошибку.

---

>> множественное наследование реализации порождает неоднозначность, тогда как множественное наследование интерфейсов - нет.

> В чем разница между конфликтными ситуациями:

> 1. класс C реализует интерфейсы A, B, в каждом из которых есть метод void f()

> 2. класс C наследует классам A, B, в каждом из которых есть метод void f()

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

dave ★★★★★
()
Ответ на: комментарий от fmj

> Учи ООП дитятко

Трепетно внимаю и спешу исполнить Ваш мудрый совет, fmj-ага.

> Без интерфейсов не будет ООП, а без наследования реализации не будет работать твоя и только твоя программа (ну еще и таких-же подростков как ты).

А то я не знал. Если ты будешь читать внимательнее, то заметишь бессвязность мыслей поста, на который я отвечал и некоторую иронию в моём посте. Или уважаемый fmj-ага впал в старческий маразм?

> Так что-же вы пытаетесь им игратся, если сами говорите, что нельзя?

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

fmj-ага, вы практически поставили всё с ног на голову. Я нигде всерьез не утверждал ничего обратного тому, что написали Вы.

watashiwa_daredeska ★★★★
()
Ответ на: Для затравки, от fmj

> небольшое сравнение языков программирования:

Это чушь полная в отношении одиночного/множественного наследования.

Можно привести аналогичное исследование, подтверждающее тезис, что колесо замедляет движение по поверхности планеты.

watashiwa_daredeska ★★★★
()
Ответ на: комментарий от watashiwa_daredeska

> Можно привести аналогичное исследование, подтверждающее тезис, что колесо замедляет движение по поверхности планеты.

Сравнение приведено, скажу по секрету, для подогревания интереса к обсуждению ;)

fmj
()
Ответ на: комментарий от watashiwa_daredeska

> Такой почтенный старичок, а не знает, что неприлично встревать в чужой разговор, не разобравшить в контексте со своими дурацкими нравоучениями.

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

fmj
()
Ответ на: комментарий от watashiwa_daredeska

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

Я понял, ты девушка, верно? Только им свойсвеннен подобный "перевернутый с ног на голову с ооочень тонким намеком" способ изложения мыслей. Если да, то я больше не буду спорить (я не считаю что девушки плохие программисты, просто не буду больше спорить).

fmj
()
Ответ на: комментарий от dave

> Ты знаком с C#?

Весьма поверхностно.

> в самом общем случае имеем три разных метода f(): A.f(), B.f() и сообственно C.f().

Вот это полный п^#%ец. За такое надо отрывать яйца.

> Какую реализацию D.f() будет наследовать C.f()? В случае C++ этот вопрос не имеет однозначного ответа

Имеет.

> (1) объект D - общий (virtual) для A и B

Тогда C.f() будет тем единственным D.f(), который имеется. No problem.

> (2) каждый из объектов A и B несет по своему собственному объекту D

В этом случае, в C будет два метода f() -- один от A, другой от B. Вызывать c.f() будет нельзя, надо явно специфицировать какой из двух методов вызывается: c.A::f() или c.B::f(). Некрасиво, но никакой неоднозначности.

Причем, заметь, суть ни на йоту не меняется от замены A, B, D на pure abstract class'ы. Ровно та же неоднозначность и ровно то же решение.

В случае же, например, CLOS или Python, вызывается метод, определяемый Method Resolution Order'ом, который четко определен. Причем, в CLOS (и в Python, вроде, тоже) MRO можно менять. Так что, никакой неоднозначности и никакого лишнего мусора.

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

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

watashiwa_daredeska ★★★★
()
Ответ на: комментарий от watashiwa_daredeska

Тогда поехали дальше.

Пусть у нас есть объект C, который наследует по две разных реализации класса D через A и B (тот случай номер 2). Теперь вводим класс E, который наследуется от A и B, но уже виртуально, т.е. в объекте E будет всего один общий объект D (случай номер 1). Затем вводим класс F, который наследуется от C и Е. Что имеем в итоге?

Объект класса F имеет уже три _разных_ подобъекта класса D:

(1) один идет от А, который идет в свою очередь через C;

(2) второй идет от B, который идет опять же через C;

(3) третий идет от A и B через виртуальное наследование от E.

Тогда что будет означать вызов F.f()?

В случае с интерфейсами все было бы в разы проще. Строго говоря, интерфейсы ((object as F) as D), ((object as C) as D) и (object as D) тоже не эквиваленты с точки зрения внешнего кода, но здесь, по крайней мере, реализация класса была бы однозначной и без ненужных осложнений.

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

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

Почему-то у многих не превращается. Значит, не все так плохо ;) По мне так такая композиция будет более понятной. Сразу видно, что за что отвечает.

dave ★★★★★
()
Ответ на: комментарий от watashiwa_daredeska

Экий ты мазохист.

Именно такая фича .NET и позволяет свести error-prone множественное наследование классов к наследованию интерфейсов. Задача решена, и она решена куда более простым и надежным методом.

---

>> Ты знаком с C#?

> Весьма поверхностно.

>> в самом общем случае имеем три разных метода f(): A.f(), B.f() и сообственно C.f().

> Вот это полный п^#%ец. За такое надо отрывать яйца.

dave ★★★★★
()
Ответ на: комментарий от dave

> Что имеем в итоге?

Три экземпляра D в экземпляре F. Вызовы методов, соответственно: f.E::f(), f.C::A::f(), f.C::B::f().

> Тогда что будет означать вызов F.f()?

Ничего. Он не скомпилируется. Всё однозначно.

> Строго говоря, интерфейсы ((object as F) as D), ((object as C) as D) и (object as D) тоже не эквиваленты с точки зрения внешнего кода

О, как ты замечательно всё свёл к интерфейсам. А теперь представь, что это всё _классы_, а не интерфейсы. Тогда ((F as E) as D) и ((F as C) as D) будут _существенно_ различны.

> здесь, по крайней мере, реализация класса была бы однозначной и без ненужных осложнений.

Давай посмотрим. В случае отсутствия множественного наследования классов, мы аггрегируем А и B в C, C и E в F. После чего, всегда явно обращаемся к соответствующему полю, типа f.c.a.f(). Заметь, мы это делаем _всегда_, даже тогда, когда у нас нет ромба наследования, нет конфликтов имен и т.п. Плюс ко всему, реализация виртуального наследования A от D становится более ручной как для пользователя класса A, так и для имплементора. Кроме того, F перестал быть наследником C и E, значит нам помимо классов, надо городить иерархию интерфейсов, которые обеспечат нам нормальный полиморфизм.

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

Итого: При замене множественного наследования на композицию мы:

1. Вводим дополнительные поля в явном виде.

2. _Всегда_ полностью специфицируем обращения к этим полям, что может приводить к весьма и весьма громоздким конструкциям, ибо в реальной жизни someSelfDescriptiveIdentifier существенно длиннее, чем просто "a".

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

Ну, а если брать модель интерфейсов, то в том же C++, она полностью аналогична Java или C# -- от pure abstract class'ов (интерфейсов) наследуются _всегда_ виртуально, поэтому никаких заморочек с несколькими экземплярами базового класса в наследнике не происходит.

> Сразу видно, что за что отвечает.

ООП было как раз придумано для сокрытия (инкапсуляции), ибо человеческий мозг имеет ограниченные возможности восприятия сложных конструкций. Если ты спрячешь пару уровней абстракции нижнего уровня, то сможешь увидеть на пару уровней абстракции верхнего уровня больше.

watashiwa_daredeska ★★★★
()
Ответ на: комментарий от dave

> она решена куда более простым и надежным методом.

Иметь _три_ _разных_ метода с одним именем в классе -- это просто? Или, может, надежно?

Либо я чего-то не понял. Ладно, гляну в книжку, как будет время.

watashiwa_daredeska ★★★★
()
Ответ на: комментарий от watashiwa_daredeska

> О, как ты замечательно всё свёл к интерфейсам. А теперь представь, что это всё _классы_, а не интерфейсы. Тогда ((F as E) as D) и ((F as C) as D) будут _существенно_ различны.

Есть существенная разница. Главный момент заключается в том, что мы всегда имеем один экземпляр класса, всего один объект (из-за одиночного наследования классов), но он может реализовывать различные интерфейсы (из-за множественного наследования интерфейсов), и представать в различных проекциях. Все остальное разруливается дотнетовской виртуальной машиной, которая берет на себя всю рутину. Сам код обычно выглядит простым (ниже).

Еще такой тонкий, но принципиальный момент. Пример с обращением к полю некорректен, поскольку поле - атрибут класса, а не интерфейса.

> Ладно, гляну в книжку, как будет время.

Это была бы неплохая идея :)

dave ★★★★★
()
Ответ на: комментарий от watashiwa_daredeska

--- start of the code ---

public interface D {
	void f();
}

public interface A: D {
	new void f(); 	// перекрываем интерфейсный метод f
}

public interface B: D {}

public class C: A, B, D {

	void D.f() { /* реализация D.f() */ }
	void A.f() { /* реализация A.f() */ }
	public void f() { /* собственно сам метод f() */ }

	public void test() {
		
		f(); 			// обращение к f()
		(this as D).f(); 	// обращение к D.f()
		(this as A).f();	// обращение к A.f()
		(this as B).f(); 	// обращение к D.f()
	}
}

--- end of the code ---

dave ★★★★★
()
Ответ на: комментарий от watashiwa_daredeska

> О, как ты замечательно всё свёл к интерфейсам. А теперь представь, что это всё _классы_, а не интерфейсы. Тогда ((F as E) as D) и ((F as C) as D) будут _существенно_ различны.

Кстати, в случае .NET эти интерфейсы, полученные от одного и тоже объекта, потенциально будут также давать различные реализации. Объект - один, иерархия объектов - строго одиночно-последовательная, а реализаций на выходе - много. Вот, такие чудеса! ;)

dave ★★★★★
()
Ответ на: комментарий от dave

Немного не так выразился. Разными могут оказаться не сами интерфейсы, а реализации метода f(), если опустить прямое преобразование к D. Так будет более правильно.

Например, если object - экземпляр C, то (object as D).f() != (object as A).f().

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

dave ★★★★★
()
Ответ на: комментарий от dave

--- start of the code ---

//
// Пример с разными реализациями одного и того же интерфейса
//

public interface D {

	void f();
}

public class E: D {

	void D.f() { /* закрытая реализация D.f() для E */ }

	public void f() { /* открытый метод f() вне связи с D.f() */ }
}

public class F: E, D {

	void D.f() { /* своя закрытая реализация D.f() */ }

	public void test() {

		this.f(); 		// вызываем основной E.f()
		(this as D).f(); 	// вызываем свою реализацию D.f()
		((this as E) as D).f(); // вызываем реализацию D.f() для E (!!)
	}
}

--- end of the code ---

dave ★★★★★
()
Ответ на: комментарий от dave

> Бедный... Какие же нехорошие оказались эти разрабы явы и шарпа! Фичу отобрали... :(

Рассмотрим еще раз этот момент. Множественное наследование позволяет выделить общую функциональность и подключать ее в те места, где она нужна. При этом соблюадается основной принцип хорошего программирования: DRY - don't repeat yourself.

Хорошие языки для меня характеризуются именно возможностью избегать копи-паста.

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

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

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

Cris
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.