yigal_s: (Default)
[personal profile] yigal_s
Действительно, надо на время забросить расистский, шовинистический, антисемитский и всякий другой необщепринятый, хотя и небесполезный в нашем нетривиальном мире, дискурс, и попинать немножко ногами что-нидь из программирования. Пинать я буду не голословно, а приводя малоизвестные и интересные (очень надеюсь) примеры.

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

Теперь к конкретным примерам.

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

Так вот, всё это очень спорная практика.

Поскольку уже в самых основах можно видеть, что подобное "двойное" наследование в одну сторону вовсе не всегда то, что нужно. Рассмотрим, к примеру, в качестве такой "основы" тип действительных и комплексных чисел. Ясно совершенно, что действительное число - частный случай комплексного. Поэтому, если оформлять их в виде классов, то действительный тип должен наследовать комплексный. Именно так и не иначе. Комплексный тип - базовый. А вот с имплементацией всё ровно наоборот, разумеется, - комплексный тип, скорее уж, использует имплементацию действительного типа (что, впрочем, в данном случае разумнее оформить не через наследование имплементации "в другую сторону", но это частности).

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

Хотя квадрат и является разновидностью прямоугольника (не говоря уже о том, что и ромба тоже), поспешно было бы наследовать квадрат от прямоугольника. И проблема тут даже не в том, что квадрату вовсе не нужно, как прямоугольнику, хранить и длину и ширину (мы уже обсудили абзацем выше, что не всегда направление наследования интерфейса и направление наследования имплементации совпадают). Проблема здесь в том, что прямоугольник в программировании и прямоугольник в математике вовсе не одно и то же. Как уже было сказано в прошлых постах ЖЖ, в математике "нет оператора присваивания", нет переменных. В програмировании же всё с точностью до наоборот. И если у нашего прямоугольника имеется метод
void Scale(real factorX, real factorY); ,
растягивающий его по ширине и высоте, то квадрат этот метод уже никак (адекватным образом) имплементировать не сможет. Неконстантный квадрат - вовсе не частный случай неконстантного прямоугольника.

[* Конец гона *]

А вот завтра пойду на работу и опять чего-нибудь унаследую.

Date: 2004-05-29 11:34 am (UTC)
From: [identity profile] dorimena.livejournal.com
а если я тут разгуляюсь с RPG ?

Date: 2004-05-29 11:40 am (UTC)
From: [identity profile] dorimena.livejournal.com
а я - болеутоляющих таблеток ))

Vot uzh voistinnu gonite , djaden'ka.

Date: 2004-06-01 07:51 am (UTC)
From: [identity profile] d-ohrenelli.livejournal.com
Во-первых, он говорит, что класс A - это частный случай, разновидность, субкласс класса Base (как, к примеру, учитель - частный случай человека), всё, что можно сделать с классом Base, можно сделать и с классом A, везде, где используется класс Base, можно использовать и экземпляр класса А.

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.

Date: 2004-06-02 08:14 am (UTC)
From: [identity profile] cousin-it.livejournal.com
Implementation inheritance has its uses. For me, it's mostly about the "template method" pattern - superclass methods using pure-virtual functionality.

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.

Date: 2004-06-02 01:06 pm (UTC)
From: [identity profile] cousin-it.livejournal.com
You're right in saying that a class shouldn't expose its internals, even to subclasses. I try to stick with that; when designing an abstract class, I make a mental note of what's (more or less) in the box and what's out.

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

Re: частичный ответ

Date: 2004-06-03 01:16 am (UTC)
From: [identity profile] cousin-it.livejournal.com
You shouldn't subclass when you don't want a clear IsA relationship :-) use delegation instead.

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.

Date: 2004-06-04 03:00 am (UTC)
From: [identity profile] cousin-it.livejournal.com
A funny analogy regarding real and complex numbers. If we're so OO, why don't we make integers a subclass of floats?

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.

Date: 2004-06-05 05:02 am (UTC)
From: [identity profile] cousin-it.livejournal.com
И действительно, почему бы и нет. Это вполне оправдано концептуально.

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.

Date: 2004-06-05 05:03 am (UTC)
From: [identity profile] cousin-it.livejournal.com
Oops, sorry for making it italic =)

Date: 2004-06-05 08:01 am (UTC)
From: [identity profile] cousin-it.livejournal.com
OK. Можете привести содержательный подтверждающий пример?(вопросы эффективности побоку, интересен именно случай, когда IsA принципиально нельзя превращать в наследование)

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 =)

Date: 2007-11-07 08:17 am (UTC)
From: [identity profile] al-zatv.livejournal.com
Мои расуждения по поводу прямоугольника/квадрата - вот http://al-zatv.livejournal.com/52115.html