Re: Re: inheritance and object creation
by tilly (Archbishop) on Feb 24, 2004 at 03:35 UTC
|
If the answer to a question is "multiple inheritance", you've probably asked the wrong question.
Really? Suppose you were to make a class hierarchy of shapes. How would order: parallelogram, rhombus, rectangle and square?
Poorly.
The problem is that as soon as we add in multiple inheritance, we add tremendous conceptual complexities in how we figure out what to do when we have defined actions in multiple places in our class hierarchy that might reasonably (under different models of how things work) apply. And it isn't that there is a right or wrong way to do this. In different situations, different ways of resolving the same ambiguity make sense. Furthermore no way of handling multiple inheritance that I have seen will always be appropriate.
Furthermore look at your example. You offer 4 basic geometric types. But would you even really want to model them with an isa arrangement? I don't know about you, but when I think of modelling a square, most of the implementations that I would start with don't turn into good implementations of a parallelogram or rhombus. Unless, of course, I started knowing that I wanted parallelograms...
The Structure and Interpretation of Computer Programming has some interesting things to say on this. In Inadequacies of hierarchies they offer a more involved version of your example and dryly note, Dealing with large numbers of interrelated types while still preserving modularity in the design of large systems is very difficult, and is an area of much current research. In the footnotes they expand on this with, Developing a useful, general framework for expressing the relations among different types of entities (what philosophers call ``ontology'') seems intractably difficult. The main difference between the confusion that existed ten years ago and the confusion that exists now is that now a variety of inadequate ontological theories have been embodied in a plethora of correspondingly inadequate programming languages.
This remark seems to me to be dead on. When you decide how to handle multiple inheritance, you are settling on a particular ontological framework. To the extent that your framework does not naturally express the understanding of the programmer, you create confusion. The subtler and more complex the distinctions that you draw, the more readily you will find that slight confusion on the part of the programmer rapidly multiplies into obfuscation, errors, and rapidly escalating design complexity. Particularly when you get to comprehension issues that virtually nobody has decent mental models of.
Do you deny that people lack good mental models of multiple inheritance? Oh, we can all produce diagrams, and for simple geometrical shapes it is easy enough to say what the inheritance diagram should be. But where is the simple diagram and natural examples that explains how traversing the class hierarchy in one order differs from traversing it in another differs from using Class::MultiMethods to locate a method based on both arguments? You know those three are different, I know they are different, and we can both produce code proving that they are different. But I can't provide an understandable model of the difference beyond, "Here are the rules and here is how the rules will apply in these cases."
Therefore I'm inclined towards merlyn's view. If you can solve your problem using single inheritance, do so. The resulting design is likely to be easier to come up with and easier for others to understand than multiple inheritance. You also avoid the potential for several kinds of "gotchas". And when you learn another language, you can design the same way secure in the knowledge that single inheritance always is present, and its behaviour doesn't tend to change that much between languages so you have less relearning to do.
| [reply] |
|
|
Furthermore look at your example. You offer 4 basic geometric types. But would you even really want to model them with an isa arrangement? I don't know about you, but when I think of modelling a square, most of the implementations that I would start with don't turn into good implementations of a parallelogram or rhombus. Unless, of course, I started knowing that I wanted parallelograms...
Yes, I want to model them in an isa arrangement. I wasn't
thinking about "modelling" a square. I was thinking about
a property system. A square has all the properties of a
rectangle. A square has all the properties of a rhombus.
Both the rectangle and the rhombus have all the properties
of a parallelogram. What is more natural than multiple
inheritance to express these facts? See also my response to
flying moose.
Therefore I'm inclined towards merlyn's view. If you can solve your problem using single inheritance, do so.
While I agree with the latter, I don't think that's what
Merlyn said. What he said was:
If the answer to a question is "multiple inheritance", you've probably asked the wrong question.
which to me means "never use multiple inheritance".
And when you learn another language, you can design the same way secure in the knowledge that single inheritance always is present, and its behaviour doesn't tend to change that much between languages so you have less relearning to do.
That I find a weak argument. I'm not shying away from hashes
because normal arrays are always present in other languages
and hashes may not. Besides, there are languages that don't
have support for even single inheritance (for the mere fact
they don't have native OO support).
Abigail
| [reply] |
|
|
Furthermore look at your example. You offer 4 basic geometric types. But would you even really want to model them with an isa arrangement? I don't know about you, but when I think of modelling a square, most of the implementations that I would start with don't turn into good implementations of a parallelogram or rhombus. Unless, of course, I started knowing that I wanted parallelograms...
Yes, I want to model them in an isa arrangement. I wasn't thinking about "modelling" a square. I was thinking about a property system. A square has all the properties of a rectangle. A square has all the properties of a rhombus. Both the rectangle and the rhombus have all the properties of a parallelogram. What is more natural than multiple inheritance to express these facts? See also my response to flying moose.
Once you decide to use @ISA, you limit choices on how to model things. Your implementation of a Square has to be the same as your implementation of a Parallelogram. Which is an inconvenient implementation of a Square.
Therefore I'm inclined towards merlyn's view. If you can solve your problem using single inheritance, do so.
While I agree with the latter, I don't think that's what Merlyn said. What he said was:
If the answer to a question is "multiple inheritance", you've probably asked the wrong question.
which to me means "never use multiple inheritance".
You seem to be misinterpreting what merlyn said.
The following type of discussion is rather common (and a variation of it happened above):
Question: How do I set up design X?
Answer: Use multiple inheritance.
Better Question: How can I modify design X to solve my original problem in a simple way with single inheritance?
What merlyn said seems to be an empirical statement that discussions like that constitute most of the cases where the answer is, "Use multiple inheritance." Therefore if that is your answer, you've probably asked the wrong question.
Incidentally I'm unclear on how you got "never" out of "probably..wrong".
And when you learn another language, you can design the same way secure in the knowledge that single inheritance always is present, and its behaviour doesn't tend to change that much between languages so you have less relearning to do.
That I find a weak argument. I'm not shying away from hashes because normal arrays are always present in other languages and hashes may not. Besides, there are languages that don't have support for even single inheritance (for the mere fact they don't have native OO support).
Good point.
I should point out that when working in languages without native OO, it is often possible to write your own OO framework. (In fact many people do it by accident.) It is easier to roll a single-inheritance scheme than a multiple-inheritance one. It is also easier to optimize single-inheritance than multiple. So if you are caught without native OO but want OO, then again it is easier to plan to design for single-inheritance.
But your basic point is true. It makes no sense to avoid features in language X just because language Y does not possess them.
However for people who have to move around between languages a lot, it does make sense to try to work with concepts that stay common. So you might add a library to your C programs giving you ready access to hashes and regular expressions, and then choose to stay away from multiple inheritance in other languages because its presence and details vary so much between Perl, Java, C++ and Python. That will limit your confusion.
If you don't need to balance multiple languages, then this issue has no bearing.
| [reply] |
|
|
|
|
|
|
The problem is that as soon as we add in multiple inheritance, we add tremendous conceptual complexities in how we figure out what to do when we have defined actions in multiple places in our class hierarchy that might reasonably (under different models of how things work) apply.
Well, surely its not too difficult to arrange that this doesnt happen? For instance MI as a means to "mix-in" stuff always seemed to me a reasonable preactice.
---
demerphq
First they ignore you, then they laugh at you, then they fight you, then you win.
-- Gandhi
| [reply] [d/l] |
|
|
For instance MI as a means to "mix-in" stuff always seemed to me a reasonable preactice.
That presumes that mixins are a reasonable practice. :-)
See Re: very simple per-object mixins for a brief discussion about whether (and when) mixins are a good idea.
The provisional thought that I responded to perrin with is still my opinion on the topic, and is also (I just realized) what I object to about complicating Perl's OO model with things like "traits" or "roles".
I should probably let that idea stew for a bit and then post a root meditation later on that...
| [reply] |
|
|
|
|
|
Re: Re: inheritance and object creation
by stvn (Monsignor) on Feb 24, 2004 at 01:13 UTC
|
| [reply] |
Re: Re: inheritance and object creation
by flyingmoose (Priest) on Feb 24, 2004 at 03:35 UTC
|
I think if you have to model the differences in polygons with an object model, you are really taking your object model too far down. The polygon class should be sufficiently advanced to take arbitrary constraints and handle all properties generically. After all, a 71-sided polygon has no name, and we didn't write an object for that yet! Nor should we have to. There shouldn't have to be classes for that, and to a mathematician, rules for a 71-sided polygon with equal angles are scarely different than a quad... the numbers just come out cleaner.
It's like the canonical OO example of cats and dogs where the user writes canine and feline in there, and writes support in there for the future inclusion of fish. It just going to darn far.
Moral of the story -- Inheritance is a powerful weapon in the OO arsenal. It is but one of many weapons, and is the most overused hammer for the proverbial nail. Abuse of inheritance and not knowing when to inherit versus encapsulate objects has led to many coding problems I've seen in my work -- folks make this error in all sorts of languages, because the power of inheritance is very alluring. It takes a lot to hold it in check -- to know when it should not be used.
| [reply] |
|
|
I think if you have to model the differences in polygons with an object model, you are really taking your object model too far down.
I didn't say polygons. I said shapes.
The polygon class should be sufficiently advanced to take arbitrary constraints and handle all properties generically.
Even if I were to be talking about polygons, I still don't
agree with that. If you follow such reasoning all the way
through, you would never subclass. Instead, you would make
you one and only base class more generic.
There shouldn't have to be classes for that, and to a mathematician, rules for a 71-sided polygon with equal angles are scarely different than a quad... the numbers just come out cleaner.
But as I said, I was thinking about shapes, and properties
of shapes. For instance, let's talk about simple symmetries.
All parallelograms have a rotational point
of symmetry (180 degrees around their center). This is not
true for all 71-sided polygons - in fact, 71 being odd, there is no 71-sided polygon that has a 180 degree rotational symmetry. Every rectangle has two lines of
symmetry - the lines dividing opposite sides, and whatever
symmetry a parallelogram has. Every rhombus has two lines
of symmetry, but different lines than the rectangle, namely
the diagonals. And in addition, it has the symmetries a
parallelogram has. Finally, a square has a 90 degree rotational symmetry, a -90 degree rotation symmetry, and the symmetries of
a rectangle, and the symmetries of a rhombus.
So, my question remains, what kind of inheritance tree
would you use? (And keep in mind, the tree is just part of
a bigger picture, there may be hundreds of other shapes as
well. If you want to collapse them into one general class,
you'll have to do a lot of case statements, and you're back
to non-OO programming).
Abigail
| [reply] |
|
|
You're mixing terms. A 71-sided polygon isn't a parallelogram, so to discard it because it doesn't have rotational symmetry is a straw man.
There are a set of properties that all polygons have (closed, etc), and a set of properties that all regular polygons have (regular angles, sides of the same length, and rotational symmetry at least 360/n degrees around the center, where n is the number of sides). This seems to lend itself to an inheritance tree, but, like the fibonacci sequence, the first approach isn't appropriate.
Instead, the more appropriate approach would seem to be to elaborate the rules that arise based on the properties of the object, not the class, and to (potentially) precalculate those results on object creation (with appropriate caching). For example, a square has certain properties. So, when an object of the ClosedShape class is instantiated that matches the properties of a square, the appropriate results would be calculated. But, it's still of the ClosedShape class.
I think that this would start to solve the generic and specific problems you have flyingmoose have raised.
------
We are the carpenters and bricklayers of the Information Age.
Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.
| [reply] |
|
|
|
|
|
|
|
No, you can still have classes and attributes, you just don't build a full inheritance tree. Again, inheritance is one of MANY tools, and only in the most basic of OO implementations is inheritance the key to the entire puzzle.
| [reply] |
|
|
|
|
| [reply] |
|
|
Ok, but what about a 73-sided polygon? I meant to say 73-sided polygon :)
| [reply] |
|
|