![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Действительно, надо на время забросить расистский, шовинистический, антисемитский и всякий другой необщепринятый, хотя и небесполезный в нашем нетривиальном мире, дискурс, и попинать немножко ногами что-нидь из программирования. Пинать я буду не голословно, а приводя малоизвестные и интересные (очень надеюсь) примеры.
Самая мерзкая вещь, которую я только знаю в ООП - это так называемое "наследование". То, что наследование нахрен не нужно, можно понять, изучив COM, прекрасно себе работающий без всякого "наследования базовых классов", а лишь посредством имплементации интерфейсов (первейшее средство обеспечения полиморфизма, между прочим). Инкапсуляция и Полиморфизм_посредством_имплементации_интерфейсов_а_не_посредством_чего_нибудь_ещё - вот, что такое ООП. "Наследование" же затесалось в представление об ООП по чистому недоразумению.
Теперь к конкретным примерам.
Когда программист "наследует" класс A от класса Base, он как бы говорит как минимум две вещи сразу. Во-первых, он говорит, что класс A - это частный случай, разновидность, субкласс класса Base (как, к примеру, учитель - частный случай человека), всё, что можно сделать с классом Base, можно сделать и с классом A, везде, где используется класс Base, можно использовать и экземпляр класса А. Во-вторых, он говорит, что имплементация класса A использует имплементацию класса Base. Тоже немаловажно - уж кто из нас не расширял функциональность класса, наследуя от неё новую, более продвинутую функциональность, использующую и старую.
Так вот, всё это очень спорная практика.
Поскольку уже в самых основах можно видеть, что подобное "двойное" наследование в одну сторону вовсе не всегда то, что нужно. Рассмотрим, к примеру, в качестве такой "основы" тип действительных и комплексных чисел. Ясно совершенно, что действительное число - частный случай комплексного. Поэтому, если оформлять их в виде классов, то действительный тип должен наследовать комплексный. Именно так и не иначе. Комплексный тип - базовый. А вот с имплементацией всё ровно наоборот, разумеется, - комплексный тип, скорее уж, использует имплементацию действительного типа (что, впрочем, в данном случае разумнее оформить не через наследование имплементации "в другую сторону", но это частности).
Представление о том, что производный класс - разновидность базового и что именно из представления о том, что является разновидностью чего, следует исходить в ОО-дизайне, вполне укоренено. А между тем, всё куда сложнее и горше.
Хотя квадрат и является разновидностью прямоугольника (не говоря уже о том, что и ромба тоже), поспешно было бы наследовать квадрат от прямоугольника. И проблема тут даже не в том, что квадрату вовсе не нужно, как прямоугольнику, хранить и длину и ширину (мы уже обсудили абзацем выше, что не всегда направление наследования интерфейса и направление наследования имплементации совпадают). Проблема здесь в том, что прямоугольник в программировании и прямоугольник в математике вовсе не одно и то же. Как уже было сказано в прошлых постах ЖЖ, в математике "нет оператора присваивания", нет переменных. В програмировании же всё с точностью до наоборот. И если у нашего прямоугольника имеется метод
растягивающий его по ширине и высоте, то квадрат этот метод уже никак (адекватным образом) имплементировать не сможет. Неконстантный квадрат - вовсе не частный случай неконстантного прямоугольника.
[* Конец гона *]
А вот завтра пойду на работу и опять чего-нибудь унаследую.

Самая мерзкая вещь, которую я только знаю в ООП - это так называемое "наследование". То, что наследование нахрен не нужно, можно понять, изучив COM, прекрасно себе работающий без всякого "наследования базовых классов", а лишь посредством имплементации интерфейсов (первейшее средство обеспечения полиморфизма, между прочим). Инкапсуляция и Полиморфизм_посредством_имплементации_интерфейсов_а_не_посредством_чего_нибудь_ещё - вот, что такое ООП. "Наследование" же затесалось в представление об ООП по чистому недоразумению.
Теперь к конкретным примерам.
Когда программист "наследует" класс A от класса Base, он как бы говорит как минимум две вещи сразу. Во-первых, он говорит, что класс A - это частный случай, разновидность, субкласс класса Base (как, к примеру, учитель - частный случай человека), всё, что можно сделать с классом Base, можно сделать и с классом A, везде, где используется класс Base, можно использовать и экземпляр класса А. Во-вторых, он говорит, что имплементация класса A использует имплементацию класса Base. Тоже немаловажно - уж кто из нас не расширял функциональность класса, наследуя от неё новую, более продвинутую функциональность, использующую и старую.
Так вот, всё это очень спорная практика.
Поскольку уже в самых основах можно видеть, что подобное "двойное" наследование в одну сторону вовсе не всегда то, что нужно. Рассмотрим, к примеру, в качестве такой "основы" тип действительных и комплексных чисел. Ясно совершенно, что действительное число - частный случай комплексного. Поэтому, если оформлять их в виде классов, то действительный тип должен наследовать комплексный. Именно так и не иначе. Комплексный тип - базовый. А вот с имплементацией всё ровно наоборот, разумеется, - комплексный тип, скорее уж, использует имплементацию действительного типа (что, впрочем, в данном случае разумнее оформить не через наследование имплементации "в другую сторону", но это частности).
Представление о том, что производный класс - разновидность базового и что именно из представления о том, что является разновидностью чего, следует исходить в ОО-дизайне, вполне укоренено. А между тем, всё куда сложнее и горше.
Хотя квадрат и является разновидностью прямоугольника (не говоря уже о том, что и ромба тоже), поспешно было бы наследовать квадрат от прямоугольника. И проблема тут даже не в том, что квадрату вовсе не нужно, как прямоугольнику, хранить и длину и ширину (мы уже обсудили абзацем выше, что не всегда направление наследования интерфейса и направление наследования имплементации совпадают). Проблема здесь в том, что прямоугольник в программировании и прямоугольник в математике вовсе не одно и то же. Как уже было сказано в прошлых постах ЖЖ, в математике "нет оператора присваивания", нет переменных. В програмировании же всё с точностью до наоборот. И если у нашего прямоугольника имеется метод
void Scale(real factorX, real factorY);
, растягивающий его по ширине и высоте, то квадрат этот метод уже никак (адекватным образом) имплементировать не сможет. Неконстантный квадрат - вовсе не частный случай неконстантного прямоугольника.
[* Конец гона *]
А вот завтра пойду на работу и опять чего-нибудь унаследую.
no subject
no subject
Date: 2004-05-29 11:38 am (UTC)no subject
Date: 2004-05-29 11:40 am (UTC)no subject
Date: 2004-05-29 11:56 am (UTC)Vot uzh voistinnu gonite , djaden'ka.
Date: 2004-06-01 07:51 am (UTC)Neverno.
Pure virtual zabyl.
Во-вторых, он говорит, что имплементация класса A использует имплементацию класса Base. Тоже немаловажно - уж кто из нас не расширял функциональность класса, наследуя от неё новую, более продвинутую функциональность, использующую и старую.
Tozhe daleko ne fakt. Tot zhe pure virtual.
Pljus ko vsemu - vse zavisit ot togo kak ty eto napishesh.
Ja shiroko ispolzuju veshi tipa IsKindOf v MFC i oni reshajut mne massu
podobnogo roda problem.
Pljus ko vsemu - ja starajus' sledovat' pravilu "abstraktnyj klass ne instanciiruetsja", chto tozhe oblegchaet rabotu v proektirovanii.
Nasledovanie - blestjashaja vesh esli umet' ej pol'zovatsja.
Re: Vot uzh voistinnu gonite , djaden'ka.
Date: 2004-06-02 04:42 am (UTC)Что именно неверно?
> Pure virtual zabyl.
Где забыл? В чем забыл? Добавьте, если вам хочется, что везде, где можно использовать ссылку на класс Base, можно использовать и ссылку на класс А, в том числе и когда они имеют "Pure virtual" методы. Что это меняет-то? Как это делает мои утверждения неверными?
> Tozhe daleko ne fakt.
Что не факт? Если я скажу, что кошки, "во вторых бывают серые", вы скажете, что не факт, поскольку "во вторых бывают и черные"?
"он как бы говорит как минимум две вещи"
Минимум. Тогда с чем же вы спорите? С тем, что указанные вещи не верны, или же не полны?
> Ja shiroko ispolzuju veshi tipa IsKindOf v MFC i oni reshajut mne massu podobnogo roda problem.
К-а-к-и-х проблем???
Опишите, как именно IsKindOf позволит организовать адекватную иерархию указанных мной математических типов и геометрических фигур.
> ja starajus' sledovat' pravilu "abstraktnyj klass ne instanciiruetsja",
Этому правилу обычно следует компилятор. :-)
> Nasledovanie - blestjashaja vesh esli umet' ej pol'zovatsja.
Смысловая часть вашего поста осталась совершенно для меня не ясна.
поправка
Date: 2004-06-02 05:30 am (UTC)Вот тут я что-то не то сказал наспех. Поскольку утверждение было куда четче:
"он как бы говорит как минимум две вещи сразу"
Т.е. суть не в переборе вариантов, как я порывался ответить изначально, а имеено в указании неотъемлемых свойств.
"Во-вторых, он говорит, что имплементация класса A использует имплементацию класса Base."
Ну и в чем же это не верно? Наследование (т.е. "имплеметацию") интерфейсов, то есть, классов без имплементаций, я не обсуждал, см. абзац 2 исходного поста.
Будете ли Вы утверждать, что я совершил принципиальную ошибку, поскольку в принципе, можно унаследоваться от класса с имплементацией, но при этом её не использовать? :-)))
no subject
Date: 2004-06-02 08:14 am (UTC)Not every IsA relationship should be implemented as subclassing, that's for sure. Java style interfaces are nice in some places, as are Ruby "mixins". Also, some problem space entities shouldn't be objects at all - OO design certainly tries to be everything, but it isn't, and sometimes we actually have to learn new things.
О! Спасибо за ответ.
О таком варианте я тоже писал вот здесь http://www.livejournal.com/users/yigal_s/217768.html?thread=349352#t349352
(я, правда, перепутал название "template method" pattern with "strategy" pattern, но суть их практически одна и та же). Но по сути такое использование наследования классов количественно ничтожно в сравнении с использованием наследования для указанных мной целей в обычной практике.
> Not every IsA relationship should be implemented as subclassing, that's for sure.
Почему? Можете привести содержательный пример?
Против интерфейсов Java я ничего не имею, я, типа, "возражаю" именно против наследования данных и имплементаций. С Ruby незнаком. Но, предполагая, о чем вы говорите, замечу, что действительно, подключение каких-то имплементаций и деклараций через наследование видится приемлемым. Другое дело, что такое подключение mixins - это по сути подключение приватных базовых классов. Даже если имплементация и используется клиентом класса, преобразование класса к базовому "mixin" бессмысленно, полагаю.
> Also, some problem space entities shouldn't be objects at all - OO design certainly tries to be everything, but it isn't, and sometimes we actually have to learn new things.
Можете привести содержательный пример? Anyway, то, о чем я говорил, то есть, числа и фигуры - вполне себе могут претендовать на статус легитимных объектов, мне так кажется.
no subject
Date: 2004-06-02 01:06 pm (UTC)Real-life example: an HTTP "servlet" base class may expose a "current_request" method to subclasses (so they can grab form parameters and stuff), but they shouldn't redefine the template method "handle_request" unless they have a very good reason.
Talking about mixins, they aren't a strictly internal thing. For example, in Ruby you can make your own collection class, which mixes in Enumerable. Then, after you define the "each" iterator for your class, you get all other iterators (collect, inject, delete_if etc.) for free. If you also define a meaningful comparison method, you magically get the "sort". And for purposes of any application, your collections become interchangeable with native Ruby arrays (were you asking about "conversion to a mixin type"?)
Regarding entities and objects: no offense intended, but numbers and shapes are textbook examples and not really useful. You just shouldn't go OO with rectangles and squares, it's not the point of OO.
One pretty common mistake of Java is that it makes programmers define anonymous methods and classes where they actually need to define a closure, or curry a function, etc. Let's say you're designing a GUI and want to create a button with a callback. Yeah, the Java designer says, let's subclass Button or whatever. But a Ruby/Smalltalk-style closure would be more suited to the task:
gui.addButton("Push Me") do
//insert code here that will execute when button is pushed
end
частичный ответ
Date: 2004-06-02 02:51 pm (UTC)Я этого не говорил. Соответственно, и дальнейшие объяснения про итераторы излишни. :-)
Речь была о другом - в С++ публичное наследование предполагает сразу две разные вещи: методы базового класса становятся публичными методами производного класса и, второе, указатель на производный класс свободно кастируется в указатель на базовый. Так вот, при использовании mix-техник, второе свойство видится практически абсолютно излишним. Реальный mix - это приватный базовый класс с, тем не менее, публичным интерфейсом (в С++ такое реализуемо, но не без малооправданных усилий). По сути, наследование тут, в mix-классах, играет чисто синтаксическую роль, позволяя изящным образом добавить какие-то фичи в имплементацию. Семантически же "базовый" и "производный" класс вовсе не связаны отношением "isA".
> Regarding entities and objects: no offense intended, but numbers and shapes are textbook examples and not really useful.
Не вижу связи между критерием употребимости, который рассматриваете Вы, и возможностью выстроить вменяемую иерархию для довольно простой системы объектов.
> You just shouldn't go OO with rectangles and squares,
Огласите, пожалуйста, весь список. (с)
> it's not the point of OO.
Да ну? А в чем тогда "the point of OO", который препятствует тому, чтобы числа и геометрические фигуры рассматривались как объекты?
Re: частичный ответ
Date: 2004-06-03 01:16 am (UTC)About rectangles and squares: I don't think "square" should even be a class. If you really need this functionality, the "rectangle" class can have a constructor that creates a square. Subclasses should not violate the contract of the superclass, this is a key requirement in OO design.
Numbers: I don't think real and complex numbers have a subclass-superclass relationship or vice versa. They're just separate entities, and you should use always explicit methods to convert between them - it makes for less confusion. From a set theory point of view, they definitely are related; but just think about the monstrous overloading of arithmetical operations. real*real = real; real*complex = complex; complex*complex can be real, but we can't tell it by checking the class, due to computational errors; etc.
This is, again, not the point of OO. OO starts to break when we consider binary operations. For a related example, consider the String class. It has a "substring" method. Now if we subclass String, an object of what class should "substring" return? What about matching a subclass of String with a subclass of Regexp? OO just isn't universal, live with it.
Re: частичный ответ
Date: 2004-06-05 04:01 am (UTC)Это верно, но по какому пункту нашей дискуссии или моего ответа Вы это говорите, я не смог понять.
> If you really need this functionality, the "rectangle" class can have a constructor that creates a square.
Вы позабыли про ромбы. Они тоже могут быть квадратами. Что до утверждения, что квадраты не должны быть классами, я бы тут не согласился. Вполне себе должны. Но, увы, не могут. По крайней мере, неконстантные.
> Subclasses should not violate the contract of the superclass, this is a key requirement in OO design.
Золотые слова. Но от них в данном случае не легче.
> Numbers: I don't think real and complex numbers have a subclass-superclass relationship or vice versa. They're just separate entities
Конечно же, нет. Любое действительное число определенно может быть использовано в качестве аргумента функции комплексного аргумента.
> complex*complex can be real, but we can't tell it by checking the class, due to computational errors
Прежде всего, мы вполне можем себе позволить не озаботиться данным случаем, то есть, считать, что complex*complex => complex. И тем не менее, желать иметь вменяемую систему иерархии численных типов. Это наше законное право.
Кроме того, система типов и точность вычислений - понятия ортогональные, мы вполне можем себе позволить рассмотреть систему типов при наличии воображаемой машины, осуществляющей точные вычисления.
Кроме того, мы можем рассмотреть систему "целых" комплексных чисел
Кроме того, мы можем реализовать ТОЧНУЮ рациональную арифметику и полностью избавиться от проблемы погрешности для некоторого класса вычислений даже на обычной машине.
> OO starts to break when we consider binary operations.
А бинарные операции здесь вообще не при чем. Проблема иерархии комплексных и действительных чисел вполне проявляет себя уже на унарных операциях.
> an object of what class should "substring" return
Я полагаю, что если есть желание, можно вернуть класс substring, не нарушив при этом инвариантов базового интерфейса. Разумеется, если имплементация такова, что возращается substring. Если же и производный класс возращает String - это еще проще, разумеется.
> What about matching a subclass of String with a subclass of Regexp
Непонятный пример.
> OO just isn't universal, live with it
И это тоже. Но некоторые из проблем, что я обсуждал, в рамках ОО вполне решаемы. Но не в рамказ парадигмы обычного наследования, что включает в себя одновременно как наследование интерфейса, так и наследование имплементации.
no subject
Date: 2004-06-04 03:00 am (UTC)I guess the reasons are obvious. A subclass-superclass relationship isn't the same as subset-superset. Moreover, as Alex Stepanov (the designer of the STL) notes, even issues of function domain can't be handled by OO alone, we need generics.
no subject
Date: 2004-06-05 04:27 am (UTC)И действительно, почему бы и нет. Это вполне оправдано концептуально.
> A subclass-superclass relationship isn't the same as subset-superset.
А кто-то чуть выше говорил что-то про "IsA". :-)
> Moreover, as Alex Stepanov (the designer of the STL) notes, even issues of function domain can't be handled by OO alone, we need generics.
А чем именно обосновывается подобный взгляд, кроме как именем авторитета? Степанов в ОО - авторитет для меня крайне сомнительный, чтоб не сказать более. Он отрицательно и пренебрежительно отзывается о парадигме ОО вообще. При всех возможных недостатках ОО, и при замечательных результатах Степанова в области generic programming, из данного интервью, у меня сложилось впечатление, что Степанов попросту не понимает, что такое ОО.
no subject
Date: 2004-06-05 05:02 am (UTC)Sounds slightly insane to me, no offense intended =) Explanations below.
А кто-то чуть выше говорил что-то про "IsA". :-)
Subclassing must be an IsA relationship. But not every IsA relationship should be subclassing =)
The point of my string/substring example is actually the same point that Alex Stepanov made in one of his interviews. The semantics for String#substring clearly demand that the returned object should have the same class as the original. AND we don't want to override the "substring" method in each subclass of String we create. This problem can't be solved without templates or generics of some kind. And it's not just about strings.
About real and complex numbers: you're clearly talking from a mathematician's point of view. I come from a math background myself, but in some cases the programmer's common sense should outweigh the matematician's rigor. Cases when we need a real-to-comlex conversion are rare enough to justify making it explicit, and it really helps readability when you come back to the code some months later.
Besides, Real isn't even intuitively a subclass of Complex, because most people don't use complex numbers in their programs. (Just as users of Integer most often don't care that it's Real.) And what happens when we add Quaternions to the soup? Should they become a superclass to Complex? Surely this is absurd.
Real and complex numbers are used in different circumstances, with different semantics and by different groups of programmers. They should not be related, because it makes life harder for the majority of people who just want reals (or integers, for that matter).
One more note about Real and Integer: the OCaml people actually decided to have different arithmetic operators for integers and reals (+. and *. vs + and *), and make all conversions explicit. This is actually nice, because every possibility of rounding error stands out in the code, and isn't implicit in the operator overloading. Most programmers use integers most of the time, and don't even notice that.
I think this explains my point about IsA relationships and subclassing. Subclassing is a practical tool for programmers to use, not a theorist's pointless exercise. Making Integer a subclass of Real doesn't make anything easier for programmers. I've seen languages where Integer and Real share a common superclass; but I haven't seen one in which they are directly related.
no subject
Date: 2004-06-05 05:03 am (UTC)no subject
Date: 2004-06-05 07:21 am (UTC)OK. Можете привести содержательный подтверждающий пример? (вопросы эффективности побоку, интересен именно случай, когда IsA принципиально нельзя превращать в наследование)
> The semantics for String#substring clearly demand that the returned object should have the same class as the original.
Это не факт. К примеру, если мы рассматриваем в качестве производного класса X строки, означающие имена людей, то отнюдь не всякая подстрока имени человека будет также являться именем человека. :-)
Аргументы в пользу generic programming сами по себе достаточно ясны. Не ясно другое - каким образом они доказывают неполноту ОО? - "issues of function domain can't be handled by OO alone". В данном примере со строками мы попросту выигрываем в объеме "ручного" кодирования. Тот же выигрыш можно было бы получить, если бы ОО-язык поддерживал "сужение" типов при использовании имплементаций в производном классе.
> Cases when we need a real-to-comlex conversion are rare enough to justify making it explicit,
Почему же "explicit"? Коль уж мы говорим о базовых и производных классах, речь как раз о неявных преобразованиях.
> and it really helps readability when you come back to the code some months later.
А какие же проблемы могут возникнуть с пониманием кода, где действительное число используется в качестве комплексного???
> Besides, Real isn't even intuitively a subclass of Complex, because most people don't use complex numbers in their programs. (Just as users of Integer most often don't care that it's Real.) And what happens when we add Quaternions to the soup? Should they become a superclass to Complex? Surely this is absurd.
Вы попросту исходите из общепринятого взгляда на наследование, который я и критикую в этом топике. Этот взгляд, в частности, предполагает, что в момент описания производного класса, все его базовые классы и интерфейсы доступны и известны (перечислены в списке наследования производного класса). Стоит лишь отказаться от этого взгляда, позволить, например, чтобы связь между базовым классом комплексных чисел и производным классом действительных чисел была введена при описании класса комплексных чисел, как ваша демонстрация "абсурда" становится нерелевантной.
> They should not be related, because it makes life harder for the majority of people who just want reals (or integers, for that matter).
И это тоже нерелевантно, в силу того же самого.
> This is actually nice, because every possibility of rounding error stands out in the code, and isn't implicit in the operator overloading.
Неявное преобразование плавающих чисел в целые, конечно же, непозволительно. Что до обратного преобразования, то никаких проблем оно, на мой взгляд, не несёт (правда, при условии, что плавающее число имеет достаточно разрядов для точного представления всех разрядов целого числа).
> I think this explains my point about IsA relationships and subclassing.
Да, это проясняет ваш взгляд, но не делает основательность ваших взглядов доказанной. В частности, если возможна потеря точности при переходе от целого типа к плавающему, то отношение IsA попросту нельзя считать вполне наличествующим.
> Subclassing is a practical tool for programmers to use, not a theorist's pointless exercise.
Какая ж практика без теории? :-)
no subject
Date: 2004-06-05 08:01 am (UTC)Take a look at this. It ain't mine, but it's nice.
Тот же выигрыш можно было бы получить, если бы ОО-язык поддерживал "сужение" типов при использовании имплементаций в производном классе.
What do you mean? Is there such a language?
Стоит лишь отказаться от этого взгляда, позволить, например, чтобы связь между базовым классом комплексных чисел и производным классом действительных чисел была введена при описании класса комплексных чисел, как ваша демонстрация "абсурда" становится нерелевантной.
Is there such a language? The idea is quite novel and I can't tell outright whether it's practical or not. A prototype would help =))
Неявное преобразование плавающих чисел в целые, конечно же, непозволительно. Что до обратного преобразования, то никаких проблем оно, на мой взгляд, не несёт (правда, при условии, что плавающее число имеет достаточно разрядов для точного представления всех разрядов целого числа).
It does create a problem: the programmer can't infer the type of the result by simply looking at an arithmetic expression. He will insert explicit casts out of fear - just like people insert dummy "catch" clauses everywhere in Java, out of fear. This leads to poor coding habits; a programming language should first and foremost be unambiguous to the human.
Да, это проясняет ваш взгляд, но не делает основательность ваших взглядов доказанной. В частности, если возможна потеря точности при переходе от целого типа к плавающему, то отношение IsA попросту нельзя считать вполне наличествующим.
Yes, that's the point. Machine integers aren't machine floats =)
Actually I think we don't disagree all that much =)
no subject
Date: 2004-06-02 02:59 pm (UTC)И в интерфейс, разумеется, тоже.
no subject
Date: 2004-06-02 11:13 am (UTC)no subject
Date: 2007-11-07 08:17 am (UTC)