[Java/C#] Множественное наследование

6yrop

как имлементят множественное наследование на Java/C#? требуется наследовать классы от 2-4 родителей.
есть ли какие паттерны для этого?

deestr

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

Dasar

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

enochka1145

[Java]
Имплементят в Java интерфейсы, а не классы, и наимплементить их можно сколько душе угодно.
В Java не то, чтобы нет множественного наследования, просто оно не пущено на самотёк, как это сделано в C++ - в Java всё это просто более регламентировано.
Конкретнее, в Java есть внутренние классы (не путать с вложенными - static - которые соответствуют вложенным классам C++ и эти внутренние классы имеют доступ к элементам внешнего класса. Пусть эти внутренние классы расширяют нужные вам классы. Чем не множественное наследование?

Dasar

> Чем не множественное наследование?
В таком случае, тяжело обеспечить полиморфизм.

enochka1145

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

Marinavo_0507

> чью функцию вызывать, если она реализована в нескольких родительских классах?
в зависимости от типа ссылки, естественно

Dasar

> его и так тяжело обеспечить
Никто и не говорил, что будет легко.
Но реализация часто встречающейся задачи через генерацию кода - это еще более страшно.

enochka1145

ОК.

class A { public: virtual void f {...} };
class B : public A { public: virtual void f {...} };
class C : public A { public: virtual void f {...} };
class D : public B, public C { public: virtual void f {...} } ;
...
ff(A* a) { a->f; ... }
...
main { D d; ff(&d); ... }
Что прикажешь вызывать?
P.S. Я задолбался писать "virtual" и "public"!

Marinavo_0507

> Что прикажешь вызывать?
А тут-то что непонятно?
У тебя в каждом классе определена реализация f, её и вызывать.

enochka1145

Извини, конечно, но что это за дизайн такой, что приходится наследовать от 2-4 родителей?
Ещё, если только мне не приснилось, в VC++ есть ключевое слово _interface. Может, оно может как-то помочь?

enochka1145

Какую именно: A.f B.f C.f или D.f?
Или все по очереди?

Dasar

есть два пути:
1. как минимум на уровне компилятора можно сказать - что нефига так писать.
2. каким-либо образом сообщить компилятору, какая функция в объединенном классе отвечает за реализацию f

Marinavo_0507

Если ссылка умеет тип D&, то значит D::f вроде логично.

enochka1145

1. Как вы, наверно, знаете, этим занимает т. н. "виртуальное наследование" - class B : virtual public A и т. д. Согласен, что это не самое ясное место в языке C++?
2. А в чём тогда отличие от Java? В обоих случаях ты можешь указать, какую функцию вызывать - я имею в виду, в реализации производного класса.

enochka1145

ОК. Я, похоже, погорячился. Не надо в D никакой функции f. Тогда что?

Marinavo_0507

Логично не допускать такого вызова.
Как сделано в C++, не помню.

Dasar

виртуальное наследование можно и выкинуть.
> А в чём тогда отличие от Java?
Тем, что кроме указания в двух-трех местах какую функцию надо вызвать, необходимо писать код для 20-100 методов, которые будут заниматься перенаправлением вызовов.
Мало того, что это бессмысленный ручной код, так это еще влияет и на производительность.

enochka1145

И как это ты его не допустишь? В C++ классы не обязаны наследовать от какого-нибудь класса Object, но любая серьёзная библиотека на C++, которую я видел, почему-то всегда этим занималась. Так что в классе D в качестве f может оказаться любая из функций этого Object.

Dasar

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

Marinavo_0507

> И как это ты его не допустишь?
Не дам определять класс D без метода f.

enochka1145

> виртуальное наследование можно и выкинуть.
Если честно, я не помню, что это. Только помню, что это отностится именно к этой ситуации.
> Мало того, что это бессмысленный ручной код, так это еще влияет и на производительность.
Ну и что? А механизм виртуальных функций не влияет на производительность?
Так что всё-таки вызываем: B.f или C.f?

Dasar

> И как это ты его не допустишь?
выдать какой-нибудь compile error 2474
> но любая серьёзная библиотека на C++, которую я видел, почему-то всегда этим занималась
посмотри тот же ATL, WTL - они все построены на множественном наследовании, но при этом у них нет таких проблем.

Dasar

> Так что всё-таки вызываем: B.f или C.f?
выдавать компиле error, пока не будет определена на уровне D функция с именем f.

enochka1145

Будем переопределять все функции Object (если их ~ 10-20)?

enochka1145

> посмотри тот же ATL, WTL - они все построены на множественном наследовании, но при этом у них нет таких проблем
Извини, конечно, но я таких умных слов пока не знаю. Если объяснишь, почему у них нет проблем, буду очень признателен.

Marinavo_0507

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

Dasar

> Так что всё-таки вызываем: B.f или C.f?
Если такая проблема есть, то обычно это означает, что в программе архитектурная ошибка.
Если класс "стриптезерша, умеющая водить" наследовать от классов Стриптезерша и Водитель тогда и будут появляться такие проблемы.
Стриптезерша, умеющая водить - это человек + навыки стриптиза + навыки вождения.

ppplva

Разумеется, ошибка компиляции.
А вообще, ты наверное имел в виду
class B : virtual public A
class C : virtual public A
Иначе приведение D* к A* неоднозначно.

Dasar

> Если объяснишь, почему у них нет проблем, буду очень признателен.
Потому что они построены по mixin-технологии.

kamputer

>любая серьёзная библиотека на C++, которую я видел, почему-то всегда этим занималась.
Забыл эпитет "морально устаревшая"

enochka1145

Так почему не создать class Human, interface AbleToDrive, interface AbleToStriptease? По-моему, это естественно. То, что ты привёл в качестве примера, больше похоже на ребёнка, появившегося в результате посещения Водителем стриптиз-клуба.

enochka1145

Внесу ясность. Программировать на C++ я бросил ещё до 98 года (когда был принят новый стандарт) - надо было поступать в МГУ. Можете считать, что я его не знаю.
Но всё-таки, ответьте, неужели в производном классе надо переопределять все функции Object, как-то: hashValue, toString и т. д.?
Если вы настаиваете, что какой-то mix-in решает все проблемы, ОК, я посмотрю, что это.

Dasar

> Так почему не создать class Human, interface AbleToDrive, interface AbleToStriptease?
Так обычно и делают.
Но дальше делают еще mixin-реализацию Mixin_AbleToDrive и Mixin_AbleToStriptease
И дальше уже "собирают" нужные классы:

class Stripteaser: Human, Mixin_AbleToStriptease{}

class Driver: Human, Mixin_AbleToDrive{}

class StripteaserAndDriver: Human, Mixin_AbleToStriptease, Mixin_AbleToDrive{}

Иначе приходится в каждом из классов заново руками прописывать все эти реализации заявленных интерфейсов

Dasar

> Но всё-таки, ответьте, неужели в производном классе надо переопределять все функции Object, как-то: hashValue, toString и т. д.?
не надо их переопределять, т.к. они есть в единственном экземляре, т.е. mixin-овые реализации абстрактные и не реализуют эти функции.

6yrop

спасибо
что такое mixin-наследование?
(сейчас в google посмотрю)

bobby

просто множественное наследование, используемое специальным образом, видимо.
в Java-то множественного наследования все равно нет

Dasar

> что такое mixin-наследование?
Определение я не помню
но на практике это выглядит, примерно, так:

class Human
{
public Coords УвидетьИОпределитьКоординаты(Предмет предмет) {..}
public void ПереместитьСебя(Coords coords);
public void ПереместитьРуку(Coords coords);
public void ПереместитьНогу(Coords coords);
}

interface IDriver
{
void ЗавестиМашину;
}

interface IStripteaser
{
void ПопрыгатьВокругШеста;
}

abstract class Mixin_Driver:
IDriver
{
public Mixin_Driver(Human human)
{
this.human = human;
}
Human human;
public void ЗавестиМашину(Машина машина)
{
human.ПереместиСебя(human.УвидетьИОпределитьКоординаты(машина;
human.ПереместитьРуку(human.Увидеть(Замок;
...
}
}

class Driver:
Human, Mixin_Driver
{
public Driver:Mixin_Driver(this){}
}


т.е. каждый из mixin-реализацией отвечает за реализацию одной небольшой способности (полной или частичной реализации одного определенного интерфейса)

Dasar

> просто множественное наследование, используемое специальным образом, видимо.
> в Java-то множественного наследования все равно нет
Как раз генераторы кода (эмулирующие множественное наследование) обычно делаются под mixin-подход, т.к. в этом подходе нет тех самых многих проблем, о которых упоминает .

enochka1145


abstract class Mixin_Driver: IDriver
{
...
Human human;
...
}

class Driver: Human, Mixin_Driver { ... }
Итого 2 human-а. Разве это хорошо? Human-то, в конце концов, всего один по задумке, если только он не шизоид...

bobby

1 Human, две ссылки

enochka1145

ОК, спасибо всем, похоже на правду...

Dasar

в языках, в которых нет множественного наследование делается следующее:

abstract НедоделанныйStripteaserAndDriver: Human
{
public НедоделанныйStripteaserAndDriver
{
this.driver = new Mixin_Driver(this);
this.stripteaser = new Mixin_Stripteaser(this);
}
Mixin_Driver driver;
Mixin_Stripteaser stripteaser;
}

далее запускается генератор с параметрами: сгенерить класс StripteaserAndDriver, взяв в качестве реализации НедоделанныйStripteaserAndDriver и добавить поддержку интерфейса IDriver и IStripteaser перенаправив вызовы IDriver-а в переменную driver, а вызовы IStripteaser в stripteaser.
Генератор соответственно делает следующих класс:

class StripteaserAndDriver:НедоделанныйStripteaserAndDriver,
IDriver, IStripteaser
{
public void ЗавестиМашину(Машина машина)
{
this.driver.ЗавестиМашину(машина);
}
public void ПопрыгатьВокругШеста
{
this.stripteaser.ПопрыгатьВокругШеста;
}

}

bleyman

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

Dasar

Если есть наследование от параметра шаблона, то так:

class Mixin_Driver<TBase>:TBase where TBase: Human{...}
class Mixin_Stripteaser<TBase>:TBase where TBase: Human{...}

class StripteaserAndDriver: Mixin_Driver<Mixin_Stripteaser<Human>>>
{
}

Dasar

> А скажика мне, какой генератор обычно используют разные умные люди?
свой, кривой
ps
на rsdn-е в рамках проекта R# что-то есть
видел еще пару портов с Java-ы, но ссылки не вспомню.

puare

Для JAVA совсем умные люди mixin аспектами реализуют. Тот же JBoss AOP это позволяет делать изначально, остальные - с некоторым гимором.

bastii

Какая проблема с множественным наследованием в Java. Есть множественное наследование интерфейсов. В реализации можно использовать делегирование. С полиморфизмом проблем не будет. Еще можно разработать протокол типа GetService, тогда возможны интересные реализации. Можно устраивать "оторванные интерфейсы", когда ресурсы под реализацию функциональности, что стоит за интерфейсом, можно выделять только если эта функцинальность востребуется.
Про ATL/WTL вообще не стоит. Там все сплошные синтаксические фокусы, в том числе и множественное наследование, по крайней мере так оно там в основном используется. Таких вещей в Java в принципе нет, как, например, и темплейтов. Так что в случае с ATL/WTL дело не во множественном наследовании. Как показывает практика, что если хорошо спроектировать фреймфорк и API к нему, что без всех эти C++ых штучек можно хорошо обходиться. Правда Java идет по другой дорожке. Всякие AOP, использование аннотаций для генереции кода. Кстати это уже проходили с ATL, не лучший способ решения проблемы кривости API. Но это, как говорится, совсем другая история

6yrop

Как показывает практика, что если хорошо спроектировать фреймфорк и API к нему, что без всех эти C++ых штучек можно хорошо обходиться.
без множественного наследования?

bastii

Нет, как раз то, что не связвно с virtual. Это я имел в виду под С++ми штучками. А мн наследование есть d Java. Нет мн наследования реализаций, что и С++ криво реализовано.

6yrop

как решается проблема в приведенном примере? или там плохое API?

6yrop

А мн наследование есть d Java. Нет мн наследования реализаций, что и С++ криво реализовано.
не думаю, что отсутствие решения лучше кривого решения
и всё таки твоя фраза
Как показывает практика, что если хорошо спроектировать фреймфорк и API к нему, что без всех эти C++ых штучек можно хорошо обходиться.
не понятна, проблема есть, о ее решении ты ничего не говоришь

bastii

какой пример?

bastii

А о какой проблеме идет речь? То что Darkgray сказал можно и ручками делать. В какой-нибудь IDEA тебе среда поможет.

6yrop

как избежать тупого набивания строчек?

bastii

Ну дык это ж Java. Зато язык простой

bastii

Потом, что так часто приходится с этим дело иметь. Если часто нужно наследоваться от, например, двух классов

class A implements IA {
public T1 m1 { ... }
...
}

class B implements IB {
public S1 n1 { ... }
...
}
то можно замутить в помощь класс

abstract class HelperBaseForAB implements IA, IB {
protected abstract IA _ia;
protected abstract IB _ib;

public T1 m1 { return _ia.m1; }
...
public S1 n1 { return _ib.n1; }
...
Дальше можно будет наследоваться от HelperBaseFromAB и реализовывать _ia _ib. Если такая гибкость не нужна, то можно сделать поля _ia, _ib.
Будут проблемы только, если имена у методов совпадаю. Таких проблем нет в C#.
Правда в Java удобнее перегрузать методы базовых классов с помошью анонимных классов.
  
class ABC extends HelperBaseForAB implements IC {
private IA ia = new A {
public T1 m1 { ... }
...
};

private IB ib = new B {
public S1 n1 { ... }
...
};
...
}
В C# так не получится

6yrop

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

Dasar

> в этих языках класс Mixin_Driver уже не абстрактный и у него не может быть абстрактных методов, которые можно перегрузить в "наследнике"?
Можно сделать абстрактным, тогда переменные также заводятся в НедоделанномДрайвере, но заполнение этих переменных возлагается на наследников.
Оставить комментарий
Имя или ник:
Комментарий: