OO practitioners often deprecate MI, but there are two situations where it's invaluable:
I used MultipleInheritance once and it blew my arm off. Not just my hand but my entire arm, just gone!
I got too close to MI and now all the hair on that side is falling out!
Oh yeah? Me, Multiple Inheritance and my wife were mutually acquainted, and now I'm pretty darn sure my kids aren't mine...
The problems that are attributed to MI are more properly put down to its facilitating sloppy factoring; if you use some decent design discipline you'll find that there's zero inherent evil in MI, as well as great economy and flexibility. -- PeterMerel
But you don't need
MI. All that you need is a way to factor interfaces. In SmallTalk
, this is trivial, since there is no typing of instances. In Java, we use single inheritance, but implement multiple interfaces. AFAIK, only in C++ and Eiffel would you use MI to solve this problem.
Generally, the reasons advanced for MI have to do with those things that you cannot
do in either Java or Smalltalk - primarily use (other than through delegation) implementations of multiple superclasses. While I agree that MI is not 'evil,' I don't see that you have even addressed the issue.
Russell's quite right here that there are other ways to do the refactoring bit, and interfaces in both java and objective-C are a good way to go, with the exception, again as he notes, of mixins. So I'm not saying the other ways of doing refactoring are bad, but since I like mixins I'll give them a little support as well:
Mixins are places where MI can cause maintenance troubles, however. If you have an abstract class that relies for its implementation on functionality in another abstract class that it obtains by way of a mixin, then you've really hidden a dependance between the two abstracts, and that's bad. In my experience, however, it's extremely rare, perhaps because I use this CycleAbstraction? technique, which naturally constrains the responsibilities of the abstracts.
- I like mixins because they let me use the STL algorithms on my own classes without having to write a bunch of translation code.
- I like 'em because they let me provide default implementations for abstract classes, again saving code.
- I like 'em especially because they let me declare data dependencies in just one place rather than all over the shop.
MI is useful for combining orthogonal abstractions (to quote BertrandMeyer
). What else is there to say? -- DaveHarris
I agree. When you've said, "to quote BertrandMeyer," you've said quite enough. :-) -- GlennVanderburg
OK, I'll say a bit more. Firstly, most of the arguments against multiple inheritance are also arguments against single inheritance. Ditto arguments for. Let's recap the pro arguments. What is inheritance for?
- Reuse of implementation.
- Reuse of type.
- Explicit type conformance.
- [How about 'Simplify conceptual model'?]
The last pair of these are only issues with static type-checking, and the first can be done quite nicely with delegation idioms (especially based around #doesNotUnderstand:), so we don't much need MI in Smalltalk. Java covers the last pair with interfaces but loses out sorely on the first. It has nothing "multiple" there. The other languages need MI because they don't have decent delegation and they do have static types. -- DaveHarris
Actually, I've said much the same thing only I phrased it as "If you're going to let people descend interface from any number of classes but implementation from only a set number, why is that set number 'one' and not some other value?" -- KatieLucas
I suspect that has something to do with the ZeroOneInfinityRule
: the idea that 'one' is the only non-arbitrary finite choice. Besides, once you allow multiple inheritance, you need precedence rules, for situations where more than one supertype defines a method. -- JeffreyHantin
Hmmm ... I think perhaps you should have let things be. In spite of my dig at Bertrand, the quote above does seem sensible to me. Your addition doesn't resonate, however, because I would never have supplied any of the three answers you give for the purpose of inheritance. They may or may not be nice benefits of inheritance, but they aren't what inheritance is for. -- GlennVanderburg
I think we're using different words for the same thing. At least, I read your long piece below without learning what inheritance is for
other than the 3 things I list. (Incidentally, I see polymorphism as all but orthogonal to inheritance.) -- DaveHarris
It seems to me that using much Multiple Inheritance might be taken as evidence of the presence in one's design of more complexity than one might actually need. It might not be evil, but it is
one of the Signs of the Apocalypse. -- RonJeffries
That's the first time I've heard anyone call a CodeSmell (or even a CodeStench) a Sign of the Apocalypse. -- JeffreyHantin
It certainly needs to be tempered by OnceAndOnlyOnce
... but how does it otherwise add complexity? -- PeterMerel
The following is a personal statement - not intended to be persuasive of others - of why I'm glad Java does not support multiple inheritance.
Although I certainly agree that MI is not evil
, I do think it is relatively unimportant and rarely useful, and I'm happy to be working in a language (Java) that does not support it. In part, I've come to believe that inheritance is given far too much importance by most OO languages, designs, developers, and pundits.
My thoughts about inheritance have been evolving since I learned Java. The revelations I've had may not seem like much to a Smalltalk programmer, but they represent a complete shift in my thinking.
Nearly every introduction to OO concepts I've ever read or seen has dealt with inheritance very early, and then moved on to a cursory discussion of "polymorphism" as a sort of nice side-effect of inheritance. Partly as a result of this (and partly because of the underlying misunderstanding) the word "inheritance" is usually used to refer to some combination of behavior inheritance and subtyping.
Java is the first language I
have used that mostly separates those two concepts. Extending or implementing an interface represents a subtyping relationship, whereas extending a class represents the more traditional combination of subtyping and behavior inheritance. The process of using and designing with interfaces has brought subtyping out of the shadows and into the foreground of my thinking.
I've begun teaching the concepts of inheritance and polymorphism the other way 'round: polymorphism and interfaces come first, as the fundamental issue, and behavior inheritance comes later, as a nice facility (more, but not much more, than a convenience) when two subtypes share significant portions of their behavior or implementation. It seems to work well. Many of the common inappropriate uses of inheritance never occur to programmers who learn it this way. I've found that the method also helps in the explanation of abstract classes and when to use them.
Consider a language that makes the separation even more clear: subtyping uses one mechanism, and behavior inheritance can be specified without
also producing a subtype relationship. (I'm speaking hypothetically, but I won't be surprised to learn that such a language actually exists.) Behavior inheritance becomes a kind of specialized composition or delegation facility. Some of the traditional objections and implementation complexities of MI disappear.
My conclusion, then, is that the strongly-typed OO community may have been going down a side path for most of its history, conflating two concepts that are actually separate. Perhaps, once we've corrected that misdirection, the time will come for MI to move back to center stage. For now, though, I'm pleased that Java avoids MI, if only because having to do without it has helped me to understand the fundamental issues more clearly. (From talking to colleagues, I believe it is having a similar effect on others, too.)
You'd might want to look at SatherLanguage
, at http://www.icsi.berkeley.edu/~sather/
Very well-reasoned and very well put, Glenn. And it has been a bad week for those dimensions.
Of course I'm contaminated now from having worked, for the past N years, in a language that is sublimely ignorant of "type". In Smalltalk, inheritance is about as close as you can get to being a programmer's hack to save code. Which is what it's for, after all.
To me, the whole subtype notion is unnecessary and (it seems to me) doesn't work anyway. When you view inheritance as a technique for implementing OnceAndOnlyOnce
, it makes eminent sense to combine things in any way whatsoever, types be damned.
Now please go over and work on that stuff about classes being cyclic dependencies. ;-> -- RonJeffries
Agreed that Glenn has put this very well. I'm content that, for almost all purposes, MI as behaviour inheritance isn't necessary. But not having it has consequences; I suspect we can trace the painful complexity of both JFC and MFC class libraries to not being able to do mixins. Among other things.
Slightly off topic: Glenn has indeed put it very nicely (perhaps we should find a shorthand for that phrase)
(trust me, that would not
pay off over time. -- GV)
but some compilers are not "sublimely ignorant" of types. In fact, they force you to think about inheritance as subtyping. Thus, generic delegation ala #doesNotUnderstand: does not work. With MI, you could do some mix-in stuff. But then, you don't have MI in Java. Which leads me to my question: Has anybody come up with something else like mixins to reduce code duplication in Java? An implementation pattern maybe? PeterMerel's comment right above seems to suggest no. -- HaskoHeinecke
I agree that inheritance is overemphasized. The problem is that reuse and polymorphic substitutability have been intermingled unnecessarily. Unfortunately, the reason for this is that code-reuse without using inheritance is freakishly verbose... but since JavaLanguage
is already freakishly verbose, nobody notices when they have to do "manual inheritance" when implementing an interface. But to me, it reeks of violating OnceAndOnlyOnce
. I've frequently run into the horrors of single inheritance in CSharpLanguage when I find out that I need to implement one of CSharp's Container interfaces. Let's say I need my class to implement an IDictionary - implementing IDictionary is horrific, and must be redone for every class that will implement the interface since they can't inherit from MyDictionaryImplementation?
because they're already trapped in my main inheritance tree. Theoretically, I could have my class contain/expose a dictionary and the dictionary contain/expose a reference back to my class... but that kind of intimate coupling is supposed to be a bad thing and most of it's usage will violate the LawOfDemeter
... and besides, it's rather tedious.
Consider a case where you have a tree of business objects, all descending from the same root "Foo", where "Foo" provides some basic services like serialization or something. "Foo" has two children, "Bar" and "Baz" that represent two orthogonal concepts - let's say one's a container and one's a curried method. Now, consider what happens when we have a class "Quux" that both needs some specific functionality from both "Bar" and "Baz" or their subclasses. Obviously, inheriting from Bar and Baz does create some confusion - it's a diamond, and diamonds are ugly.... but if you refactored Baz's functionality into a MixIn
(say, bazMixIn), then there's no diamond. Baz and Quux both inherit from bazMixIn, but Baz inherits from Foo while Quux inherits from Bar.
Java actually does
support multiple inheritance. It just limits that support to interfaces and doesn't provide it for inheritance of implementation. It's a way of allowing polymorphism strictly by adhering to a shared interface "contract" without having to share any implementation of structure or behavior. It's still MI, it's just for subtyping instead of subclassing. The problem of name-clashes and redefinition still exists.
Actually, the problem of name-clashes does not exist (at least with functions). Because interfaces only prototype the function, the same function cannot exist twice with the same prototype and different implementations. Because there is no implementation. And if the same function exists twice with different prototypes, who cares? Of course, I could be wrong. I haven't used Java in a long time... -- DorKleiman
[If one interface has a method called "buildArchive" that builds an archive and another interface has a method called "buildArchive" that archives a build, the implementation of those two methods should not
be the same. Just because the same characters were chosen to identify the operations doesn't mean they represent the same operation. Java needs a way to specify which interface an implementation should be associated with.]
Note that there are really two facets of inheriting or sharing "implementation": one is inheriting structure or state (data) and the other is inheriting behavior (method implementation). There are some languages that provide inheritance of behavior but not of structure (Perl and Python spring to mind). This is kind of interesting because it forces superclass and subclass to communicate only
via methods and you are forced to pay more attention to the interface
between superclass and subclass (which more people need to do IMHO).
Ralph Johnson once said that C++ programmers suffer from the disease of thinking that inheritance causes polymorphism :-). Like Java, the Sather programming language (an interesting variant of Eiffel that preceded Java) explicitly separates subtyping from subclassing. In fact I believe it even does what Glenn mused about above: allowing subclasses to share more than just interface but without forcing a subtype relationship.
Inheritance isn't really a "pure" concept in OOPL theory. But Polymorphism is. The orthogonal concept is that of "Delegation." Inheritance of behavior is nothing more than a particular combination of Polymorphism and Delegation. Having separate support for both polymorphism and delegation and being able to combine them is actually much more powerful (meaning you can do more with it; including get yourself into more trouble ;-) -- BradAppleton
I enjoy working in a typeful language, and find that it helps my thinking (and I have worked some in Smalltalk and some other typeless ones, so I do know a little bit, at least, about the other side of the fence). One of the things I'm excited about is the idea that, with proper separation of the concepts, inheritance can still be "a technique for implementing OnceAndOnlyOnce" without interfering with proper subtype relationships.
One thing to note is that if your design is expressed in terms of interfaces rather than classes, you do not need MI to do mixins in Java; delegation works just fine. The fact that delegation is manual and tedious can be ameliorated by tools (or possibly by the eventual addition of AutomaticDelegationForJava). Not that MI wouldn't be nice for doing mixins, but it's not necessary for the best Java designs I've seen.
Unfortunately, Java developers (me included) are still learning how to use interfaces well, how to express designs and relationships in terms of pure interfaces rather than these odd mixes of interface and behavior that we call classes. Not having MI is forcing us to learn. I think over the long run this will be useful. -- GlennVanderburg
Do you agree that it's led to unnecessary complexity in the libraries?
Yeah, but I don't think the complexity is necessarily the result of not having MI; I think the libraries would have been simpler if interfaces had been put to better use. I *think* that; I don't know that I can really back it up yet.
Note: C++ is a language in which inheritance need not produce a subtyping relationship. They call that kind "private" or "protected" inheritance. C++ offers a superset of Java in this area. -- DaveHarris
Um ... yeah, I knew that. :-) I really did, and just forgot. But I see that (my forgetting, that is) as part of the problem, which I'll get to in a moment.
In a way, C++ offers the complete separation in the other way, because you can introduce a subtype relationship without inheriting behavior, by using pure abstract classes.
So C++ technically matches what I speculated about above. But while the concepts of inheritance and polymorphism can be separated in a C++ program, they are not conceptually separated in the language. And that conceptual separation is what I think we need. -- GlennVanderburg
C++ has much more polymorphism that you think (or remember) it has, thanks to overloading and templates. And polymorphism in STL is quite separated from the inheritance concept. -- NikitaBelenki
And no one ever uses the same interfaces or protocols in Smalltalk between two or more classes that share the same method-names but don't share implementation?
Of course they do. I think that's called polymorphism. How do different methods of the same name lead us to want multiple inheritance? Is the idea to build an abstract class for the interface, so they can all inherit #implementedBySubclass?
One problem with multiple inheritance of interface only, as found in Java, is that an interface contract cannot usually be specified completely without using code. For example Stack.pop() needs the precondition that the stack be not empty, and likewise for the post-condition of Stack.push(). A Stack interface ought to be able to carry the code to enforce the contract.
This doesn't necessarily mean we need full MI, but it does mean we need more than Java provides.
When I worked in C++, I did use multiple inheritance, for the "orthogonal abstractions" reasons cited above. So when I first came to Java, I felt stifled. But then I learned that you could get the same results by composition (~= delegation), and since then I've felt it made me a better OO developer. -- TomRossen
Back in March of 2001, an exchange on MultipleInheritanceInSmalltalk
supported the argument that MultipleInheritanceIsNotEvil
. Here are two examples culled from that page:
- Consider the design of class "Matrix". It has rows, columns, elements, and can be enumerated and pivoted. Thus, it certainly wants to behave like a collection. It also participates in arithmetic operations ... instances respond to "+", "-", "=", and so on (ignoring dot- and cross- products for the time being).
- Consider the implementation of "Complex", which wants to act like both an Association and a Magnitude.
These are two cases where not only can we correctly say MultipleInheritanceIsNotEvil
, but in my opinion these two classes are cases where MultipleInheritance
the best and most supportable solution. Yes, it can be simulated through delegation or protocol-copying. But the basic concept is still MultipleInheritance
I would think that anyone who has endured the pain and suffering of implementing and supporting either class in either Smalltalk or Java (don't forget about the differences between the various flavors of numbers and the "zoo" of iterator/collection classes) would be well aware of the compelling advantage offered by MultipleInheritance
in these two cases.
Personally, I use MI a LOT. I use it for adding a lot of genericity to things without needing to add code to every use of it. Suddenly I can have, for example, a template to descend from that will keep all instances of a class in a suitable dictionary so they can be found, without needing to do anything but descend from it. Without MI, any classes that use that couldn't have any other parents of any size. I don't find getting loops in the graph a problem, partly because of the way I code - I extract behaviours into very small precise classes, and layer them.
Whenever I go near SI languages, I find myself spending a lot of time writing a lot of code over and over again in order to get polymorphic behaviour to inherit so I can reuse stuff, which seems like a waste of time to me...
And you end up with arbitrary decisions about where to slice the inheritance. Is a PoliceCar?
mostly a Car (has four wheels, engine, drives on roads) or an EmergencyVehicle?
(has a radio, blue lights and a siren). Now the correct answer in an SI environment is that NEITHER is more important, and so people should interface-inherit from both and rewrite both behaviours. In the real world I find that actually people pick one. Deem it "more important" and implementation-inherit that. Meanwhile, a partition away, someone else is making a different importance decision and by the wall at the end of the office buried in nine tons of other paper is a "policy" on how to pick them written by a committee...
Using public inheritance rather than delegation, as a tool for reusing implementation requires fewer lines of code in C++. This encourages the developer to use inheritance even when they have no intention of refining the type they are inheriting from.
I think that multiple inheritance of implementation tends to cause a very confusing model of the system - increasing maintenance costs. On the other hand, reuse by delegation in C++ tends to involve repeated descriptions of the same elements - increasing maintenance costs by violating DontRepeatYourself
I tend towards using delegation rather than inheritance because I tend to work on systems that will be maintained by low-skill developers who will not be dedicated to a single project and thus need to understand the model very swiftly.
I'm still curious about this idea that instead of making the design clearer (with some decent class diagrams, for example) the code has to stand on its own and therefore must be verbose (and hence more error prone) and "simple". It produces a "simplicity" akin to geometry textbooks of the pre-1900s where all the diagrams are described in massive amounts of longhand English instead of just being drawn. It's perhaps due to the annoyance of trying to put diagrams into the code repository (which is an analogue of the textbook situation), but there's something faintly bonkers about using a 1000 lines of code to save a single diagram...
I have issues with inheritance in general for exactly the slicing problems raised above. Instead of the arbitrary slicing allowed by multiple inheritance I would lean towards removing as much inheritance as possible. After all, is it important to know that every police car has four wheels or is it sufficient to say that a police car is a car and cars usually have four wheels. For my mind inheritance seems to incorporate a lot of redundant information.
This sounds a little extreme. Instead of removing as much inheritance as possible, I would focus on code clarity and add or remove inheritance to help understanding of the code.
Katie and Richard's comments are why I find myself wanting to do multiple inheritance (in Java or C#) but feel I must start removing inheritance or combining base classes. So I end up with thousands of lines in a widely-shared base class. Then, the code is more flexible for future changes. Why don't I refactor using delegation? Because it is too complex/abstract except for major high-level structure. Consider this example: I come upon a class which already inherits from a class, then need to make the first class inherit from a second existing class. I am forced to refactor all three classes (plus any others involved in the inheritance tree) in order to get the effect of multiple inheritance. Switching to delegation means even more refactoring. I feel we don't have a good solution to this whole topic, as illustrated by the amount of varied comments and approaches. -- DaveEaton
Maybe I'm missing something, but isn't AspectOrientedProgramming
just another, cleaner, way to do something equivalent to MultipleInheritance
Can you explain why you think it might be?
Answer: Sure, AOP and the mixin style of MI are closely related. Of course that just means that AspectOrientedProgrammingIsEvil?
, too. -- AndersMunch
Sure, and since AopIsMulticaster, MultiCaster is evil too. And since MultiCaster just generalizes ObserverPattern, we see the whole evil thing goes back to the all-seeing eye of Sauron. That rings a bell ...
On the other hand ... I suggest graphically that AOP and MI are unrelated. Consider an MI hierarchy; it's a DirectedAcyclicGraph. Now let's decorate nodes in the DAG with cutpoints and define another DAG to add semantics to the cutpoints. The two DAGs are plainly orthogonal; hence MI != AOP.
My approach to working around a lack of multiple inheritance: destroying the LawOfDemeter
. If you want to inherit from two classes, make two classes - and make them richly, tighly, deeply, romantically coupled. And explose each other to the outside world as public get-accessors, rather than forwarding a laundry-list of methods. Possibly wrap them in a larger, outside object if you find that a useful mental trick. PoliceCar?
is two classes - PoliceCar?
::Police and PoliceCar?
::Car, where an instance of Car and Police are constrained to be tied to each other. Treat them mentally as a single class, a single object. Often, you'll find that the coupling is less than you expected, and that the classes are individually functional and useful, and you get a more versatile system than you'd even have gotten with the MI approach.