Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Прочее";
Текущий архив: 2010.08.27;
Скачать: [xml.tar.bz2];

Вниз

[ООП] Хочу странного   Найти похожие ветки 

 
Некто   (2010-03-08 13:27) [0]

При спорах вида "С++ vs X", где Х - любой объектно-ориентированный ЯП, поддерживающий единичное наследование, неизбежно возникает ответвление спора на тему "множественное наследование vs. единичное наследование". Защитники МН упирают на бОльшую гибкость и возможности, которые дает МН, противники - на то, что МН усложняет реализацию языка, сам язык и что МН вообще не нужно.

Мое личное мнение состоит в том, что если рассматривать классическое ООП в стиле Smalltalk, то правы оппоненты МН. Если же рассматривать ООП и как инструмент создания архитектурного каркаса ПО, то я выступаю за МН, ибо он дает хорошие возможности по комбинации функциональности, в частности реализуя возможность mixin-style.

Теперь, собственно, к странному. А надо ли вообще в языке иметь наследование реализации в том виде, в котором это сделано в том же C++/C#/Deplhi/Java т.д.? Что в этих языках имеется в виду, когда говорится "Класс Б наследует класс А"? Имеется в виду две вещи:
1. Класс Б является подтипом А, т.е. поддерживает интерфейс класса А, и является легальным для применения везде, где можно применять А. Это Liskov substitution principle (http://en.wikipedia.org/wiki/Liskov_substitution_principle).
2. Класс Б наследует всю реализацию класса А, его поля и методы, т.е. повторно использует реализацию класса А.

Другими словами, в таком наследовании мы имеем смешение двух идей:
1. Абстракция через обобщение.
2. Повторное использование кода.

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

Данный подход не имеет тех недостатков МН, на которые упирают его противники и в то же время он обеспечивает все его возможности + кое-что сверху. Ваши мнения, коллеги?


 
Alkid ©   (2010-03-08 13:28) [1]

Тьфу, стартовый пост от моего имени :)


 
Kerk ©   (2010-03-08 13:31) [2]

Честно говоря, я не понял вопрос.
Лучше ли композиция объектов плюс множественное наследование интерфейсов, чем просто множественное наследование классов?


 
Piter ©   (2010-03-08 13:34) [3]

мое мнение, что в дельфи сделано оптимально. Она не имеет множественного наследования, но имеет множественное наследование интерфейсов.


 
Piter ©   (2010-03-08 13:35) [4]

а вопрос действительно непонятный. Alkid ушел куда-то совсем в дебри философии. Сам ведь сказал что "МН vs АнтиМН" не могут договориться, а тут то вообще тема высоких материй затрагивается


 
Anatoly Podgoretsky ©   (2010-03-08 13:35) [5]

А нам татарам все равно.


 
Alkid ©   (2010-03-08 13:48) [6]

Я имел в виду следующее: наследование классов как таковое не нужно. Нужна композиция и делегирование. Собственно тогда вопрос "МН vs. ЕН" снимается само собой за упразднением наследования :)


 
Игорь Шевченко ©   (2010-03-08 13:51) [7]

Есть множественное наследование интерфейса и множественное наследование реализации. Хорошо и то, и другое.

Тут вроде все написано:

http://ru.wikipedia.org/wiki/%D0%9C%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%B5%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BD%D0%B0%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5

и тут:

http://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81_(%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)


> Другими словами, в таком наследовании мы имеем смешение
> двух идей:
> 1. Абстракция через обобщение.
> 2. Повторное использование кода.


Имеем смешение одной идеи - наследование :)


 
Kerk ©   (2010-03-08 13:52) [8]


> Alkid ©   (08.03.10 13:48) [6]

Наследование еще нужно и для типизации ведь.


 
Alkid ©   (2010-03-08 13:59) [9]


> Piter ©   (08.03.10 13:34) [3]

Да вот не оптимально. Приведу пример из практики. Там C#, но в нем возможности те же. Так вот, есть интерфейс во фреймворке стандартный интерфейс INotifyPropertyChanged, который объявляет событие, уведомляющее подписчиков об изменении свойства объекта. Есть разные реализации этого интерфейса, опирающиеся на разные механизмы (Linq, Reflection). Предположим, что класс называется MyNotifyPropertyChanged.

Далее, есть класс:

public class MyBusinessClass : MyBusinessClassBase, INotifyPropertyChanged
{
...
}

Вопрос: как мне "подмешать" в свой класс реализацию MyNotifyPropertyChanged, что бы он заимплементил INotifyPropertyChanged? В терминах С++ это решается так:

class MyBusinessClass : public MyBuinessClassBase, public virtual INotifyPropertyChanged, private virtual MyNotifyPropertyChanged
{
...
};


 
Alkid ©   (2010-03-08 14:02) [10]


> Kerk ©   (08.03.10 13:52) [8]
> Наследование еще нужно и для типизации ведь.

Это наследование интерфейса. Его я не предлагаю упразднять :)


 
Alkid ©   (2010-03-08 14:11) [11]


> Игорь Шевченко ©   (08.03.10 13:51) [7]
> Есть множественное наследование интерфейса и множественное
> наследование реализации. Хорошо и то, и другое.

Но при этом множественное наследование имеет свои сложности, которые хорошо бы ликвидировать.


> Имеем смешение одной идеи - наследование :)

Со своими недостатками :)

Напримр, почему в Дельфи при наследовании класса я обязан получать подтип, даже если я хочу только его реализацию? Потому что наследование только открытое :) Я не могу унаследовать только реализацию, "интерфейс" идет "в нагрузку".


 
sniknik ©   (2010-03-08 14:12) [12]

> В терминах С++ это решается так:
а в реальной жизни это бывает нужно?
почему интересуюсь, у нас 2 "чистых" сишника, и оба против использования множественного наследования (обсуждали как то), в смысле "лучше не использовать чтобы не путать логику программы" и в итоге результат как с "goto" вроде бы можно, но реально за всю рабочую деятельность обоим не пригодилось... (мне в дельфи отсутствие тоже в общем то не мешало, и не хотелось, типа "вот эту задачу(реальную) без множественного решить сложно")

в общем встречный вопрос, а есть ли смысл в теоретизировании?


 
Alkid ©   (2010-03-08 14:17) [13]


> sniknik ©   (08.03.10 14:12) [12]

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

Что же касается "чистых сишников", то я не удивлюсь - на С нету наследования вообще :)


 
Игорь Шевченко ©   (2010-03-08 14:37) [14]

Alkid ©   (08.03.10 14:11) [11]


> Но при этом множественное наследование имеет свои сложности,
>  которые хорошо бы ликвидировать.


Ровно одну сложность, описанную по первой ссылке. Ну так "не делать так".


> Напримр, почему в Дельфи при наследовании класса я обязан
> получать подтип, даже если я хочу только его реализацию?
>  Потому что наследование только открытое :) Я не могу унаследовать
> только реализацию, "интерфейс" идет "в нагрузку".


Классические пуристы возражают против наследников, например, от TList. Их аргументы таковы, что наследнику в большинстве случаев не нужны методы предка Exchange, Move, и т.д.

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


 
Anatoly Podgoretsky ©   (2010-03-08 14:41) [15]

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


 
Alkid ©   (2010-03-08 15:09) [16]


> Игорь Шевченко ©   (08.03.10 14:37) [14]
> Ровно одну сложность, описанную по первой ссылке. Ну так
> "не делать так".

Там несколько проблем перечислено, какую из них ты имеешь в виду?


> Классические пуристы возражают против наследников, например,
>  от TList. Их аргументы таковы, что наследнику в большинстве
> случаев не нужны методы предка Exchange, Move, и т.д.

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


 
Игорь Шевченко ©   (2010-03-08 15:15) [17]

Alkid ©   (08.03.10 15:09) [16]

Увидел одну, "проблему алмаза"


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


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

Поэтому иногда заветам пуристов можно и не следовать.

Оно же, ООП для чего вообще появилось на свет - чтобы сложность уменьшить. Вот и давайте уменьшать :)


 
имя   (2010-03-08 19:03) [18]

Удалено модератором


 
Некто   (2010-03-08 19:03) [19]


> Игорь Шевченко ©   (08.03.10 15:15) [17]
> Увидел одну, "проблему алмаза"

Ну есть и другие. Например, способ разрешения совпадающих имен.


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

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


> Поэтому иногда заветам пуристов можно и не следовать.

Я бы ЭТО пуризмом не назвал :)


> Оно же, ООП для чего вообще появилось на свет - чтобы сложность
> уменьшить. Вот и давайте уменьшать :)

Так этим и занимаюсь! :)


 
Игорь Шевченко ©   (2010-03-08 19:37) [20]

Некто   (08.03.10 19:03) [19]


> Написанный и покрытый юнит-тестам  код сам по себе проблемы
> не будет представлять, даже если он будет больше альтернативного.
>  С другой стороны, если он раз и навсегда закроет "дыры",
>  вылезающие из-за нежелательного раскрытия интерфейса TList,
>  то он в корне устранит возможные ошибки, вызванные неправильными
> его использованием из других мест в программе. Эти ошибки
> опаснее.


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

Мне иногда проще написать наследников, оставляя потенциально лишнюю функциональность, чем выполнять композицию. Я надеюсь, Аллах в это время отвернется :)

Это с одной стороны.

С другой стороны не так давно видел хорошее высказывание

"I"ve always been an advocate of the school that says "write more code when preparing an infrastructure so you can write less code when using this infrastructure""

найти бы компромисс :)


 
vuk ©   (2010-03-08 19:48) [21]

to Игорь Шевченко ©   (08.03.10 15:15) [17]:

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

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


 
cwl ©   (2010-03-08 19:49) [22]

> Игорь Шевченко ©   (08.03.10 19:37) [20]
"I"ve always been an advocate of the school that says "write more code when preparing an infrastructure so you can write less code when using this infrastructure""
часто цитируемый вами МакКоннел в начале книги и пишет про расширяемость - в чем же проблема :>
// или это он и есть в английском варианте


 
Игорь Шевченко ©   (2010-03-08 20:12) [23]

cwl ©   (08.03.10 19:49) [22]


> или это он и есть в английском варианте


Нет, это не он, это из комментариев к
http://17slon.com/blogs/gabr/2007/03/fun-with-enumerators-part-3.html

кстати, хороший блог по Delphi

vuk ©   (08.03.10 19:48) [21]

"Когда в середине 1980-х годов С++ впервые был выпущен в свет, объектно-ориентированные языки программирования были широко разрекламированы как радикальное средство борьбы против сложности программного обеспечения".

Эрик Реймонд, "Искусство программирования для Unix"

Буч в своем бестселлере примерно о том же пишет, только менее радикально :)


 
vuk ©   (2010-03-08 20:17) [24]

to Игорь Шевченко ©   (08.03.10 20:12) [23]:
Вот с борьбой со сложностью согласен. Но борьба со сложностью не равна упрощению. Как-то так.


 
Игорь Шевченко ©   (2010-03-08 20:21) [25]

vuk ©   (08.03.10 20:17) [24]

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


 
vuk ©   (2010-03-08 20:43) [26]

to Игорь Шевченко ©   (08.03.10 20:21) [25]:

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


Именно.


 
oxffff ©   (2010-03-08 21:16) [27]


> Alkid ©   (08.03.10 13:59) [9]
>
> > Piter ©   (08.03.10 13:34) [3]
>
> Да вот не оптимально. Приведу пример из практики. Там C#,
>  но в нем возможности те же. Так вот, есть интерфейс во
> фреймворке стандартный интерфейс INotifyPropertyChanged,
>  который объявляет событие, уведомляющее подписчиков об
> изменении свойства объекта. Есть разные реализации этого
> интерфейса, опирающиеся на разные механизмы (Linq, Reflection).
>  Предположим, что класс называется MyNotifyPropertyChanged.
>
>
> Далее, есть класс:
>
> public class MyBusinessClass : MyBusinessClassBase, INotifyPropertyChanged
> {
> ...
> }
>
> Вопрос: как мне "подмешать" в свой класс реализацию MyNotifyPropertyChanged,
>  что бы он заимплементил INotifyPropertyChanged? В терминах
> С++ это решается так:
>
> class MyBusinessClass : public MyBuinessClassBase, public
> virtual INotifyPropertyChanged, private virtual MyNotifyPropertyChanged
> {
> ...
> };


В Delphi есть делегирование реализации implements, если я правильно понял вопрос.
В остальном я скоро приеду и у нас будет возможность посидеть и обсудить это. :)


 
oxffff ©   (2010-03-08 21:34) [28]


> Игорь Шевченко ©   (08.03.10 14:37) [14]
> Alkid ©   (08.03.10 14:11) [11]

> Нету в Delphi возможность понизить область видимости - ну
> такой язык. Зато есть возможность ее повысить, что довольно
> регулярно используется.


Этой возможности быть и не должно. :)
Поскольку, если B является подклассом A, то B является подтипом A.
Если позволить менять видимость, то тогда B не будет являться подтипом A.
Поскольку B не может выступить в качестве А
(нарушение типобезопасности, то есть язык будет являться небезопасным)


 
Игорь Шевченко ©   (2010-03-08 22:03) [29]

oxffff ©   (08.03.10 21:34) [28]

То же самое, только по-русски можно ?


 
oxffff ©   (2010-03-08 22:22) [30]


> Игорь Шевченко ©   (08.03.10 22:03) [29]
> oxffff ©   (08.03.10 21:34) [28]
>
> То же самое, только по-русски можно ?


:)

Например

A=class
public
InstanceFieldA:integer;
end;

B=class(A)
change ability to private
InstanceFieldA;
end;

И есть функция Foo(obj:A). То в эту функцию передать экземпляр B нельзя поскольку B не может вести себя как A(то есть быть подтипом). Поскольку предполагается что функция принимает нечно у которого есть открытый доступ к InstanceFieldA. Мы не можем передать в функцию B, поскольку открытого доступа к InstanceFieldA нет. То есть хотя B является подклассом, B не является подтипом, в противном случае будет нарушение инкапсуляции, поскольку будет раскрыт доступ к закрытой части.


 
jack128_   (2010-03-08 22:40) [31]


> То же самое, только по-русски можно ?

Принцип Лисковой вроде это -)


 
Alkid ©   (2010-03-08 22:45) [32]


> oxffff ©   (08.03.10 21:34) [28]

Не пугай меня. Есть С++ с закрытым наследованием. Так что ты можешь мне написать пример, иллюстрирующий нетипобезопасность закрытого наследования :)


 
Alkid ©   (2010-03-08 22:51) [33]


> oxffff ©   (08.03.10 22:22) [30]

В общем, подкласс и подтип - это в общем случае не синонимичные понятия. В языках типа Дельфи, C#, Java - это так, но в С++ уже не так и можно совершенно легально порождать подклассы, не подчиняющиеся LSP.


 
Игорь Шевченко ©   (2010-03-08 23:09) [34]

Alkid ©   (08.03.10 22:45) [32]

Опередил :)

Дети обязательно должны наследовать реализацию родителей, и не обязательно должны наследовать их интерфейс.

Что, собственно и в жизни наблюдается :)


 
oxffff ©   (2010-03-08 23:13) [35]


> Alkid ©   (08.03.10 22:45) [32]
>
> > oxffff ©   (08.03.10 21:34) [28]
>
> Не пугай меня. Есть С++ с закрытым наследованием. Так что
> ты можешь мне написать пример, иллюстрирующий нетипобезопасность
> закрытого наследования :)


Ты можешь сам написать такой пример ориентируясь на [30].


 
oxffff ©   (2010-03-08 23:16) [36]


> Alkid ©   (08.03.10 22:51) [33]
>
> > oxffff ©   (08.03.10 22:22) [30]
>
> В общем, подкласс и подтип - это в общем случае не синонимичные
> понятия. В языках типа Дельфи, C#, Java - это так, но в
> С++ уже не так и можно совершенно легально порождать подклассы,
>  не подчиняющиеся LSP.


Вообще нужно говорить наверно о теории типов, а не о LSP.
Так вот, то что положено в С++ это решение автора со всеми вытекающими.
Так же как и в JAVA знаменитая проблема с подтипами массивов.


 
oxffff ©   (2010-03-08 23:20) [37]


> Игорь Шевченко ©   (08.03.10 23:09) [34]
> Alkid ©   (08.03.10 22:45) [32]
>
> Опередил :)
>
> Дети обязательно должны наследовать реализацию родителей,
>  и не обязательно должны наследовать их интерфейс.
>
> Что, собственно и в жизни наблюдается :)


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


 
Игорь Шевченко ©   (2010-03-08 23:29) [38]

oxffff ©   (08.03.10 23:20) [37]


> Есть формальная теория рекомендую к ней обратиться.


Есть синтаксис конкретных языков. Можно я буду к нему обращаться ? :)


 
oxffff ©   (2010-03-08 23:30) [39]

Вот кратко

There is one kind of change that can be made to methods in subclasses that
we have not yet considered. Rather than changing the types of methods, we
can consider changing the visibility of methods.
We have not discussed this earlier because our defaults have been that all
methods are visible. We will continue with those defaults until section 14.4,
but sincemost object-oriented languages do allowthe programmer to specify
degrees of information hiding, we briefly address this question in terms of
preserving safety in subclasses.
It is easy to see that taking methods that are hidden inside superclasses
and making them more visible in subclasses causes no problems with inherited
methods or even in having subclasses generate subtypes. What is more
interesting is that, if we do not care whether subclasses generate subtypes,
we can also make methods less visible in subclasses.


Выделенное говорит о том, что если B является подклассом A, но не является подтипом А мы не может передать B в функцию которая ожидает A.
Если это допустить, то будет нарушение типов.

Читайте теорию господа.


 
oxffff ©   (2010-03-08 23:31) [40]


> Игорь Шевченко ©   (08.03.10 23:29) [38]
> oxffff ©   (08.03.10 23:20) [37]
>
>
> > Есть формальная теория рекомендую к ней обратиться.
>
>
> Есть синтаксис конкретных языков. Можно я буду к нему обращаться
> ? :)


Ради бога. Есть теория можно я буду к ней обращаться?



Страницы: 1 2 3 вся ветка

Форум: "Прочее";
Текущий архив: 2010.08.27;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.59 MB
Время: 0.061 c
15-1272141003
Юрий
2010-04-25 00:30
2010.08.27
С днем рождения ! 25 апреля 2010 воскресенье


2-1270109240
Цукор5
2010-04-01 12:07
2010.08.27
Картинка на форме


15-1271370139
Германн
2010-04-16 02:22
2010.08.27
Неужели?


2-1265350338
Mery
2010-02-05 09:12
2010.08.27
сворачивание программы на панель задач


2-1267379618
Pavel
2010-02-28 20:53
2010.08.27
Загрузка данных из файла в поток





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский