155

I've always liked the idea of having multiple inheritance supported in a language. Most often though it's intentionally forgone, and the supposed "replacement" is interfaces. Interfaces simply do not cover all the same ground multiple inheritance does, and this restriction can occasionally lead to more boilerplate code.

The only basic reason I have ever heard for this is the diamond problem with base classes. I just can't accept that. To me, it comes off an awful lot like, "Well, it's possible to screw it up, so it's automatically a bad idea." You can screw up anything in a programming language though, and I mean anything. I just cannot take this seriously, at least not without a more thorough explanation.

Just being aware of this problem is 90% of the battle. Furthermore I think I heard something years ago about a general-purpose work-around involving an "envelope" algorithm or something like that (does this ring a bell, anyone?).

Concerning the diamond problem, the only potentially genuine problem I can think of is if you're trying to use a third-party library and can't see that two seemingly unrelated classes in that library have a common base class, but in addition to documentation, a simple language feature could, let's say, require that you specifically declare your intent to create a diamond before it'll actually compile one for you. With such a feature, any creation of a diamond is either intentional, reckless, or because one is unaware of this pitfall.

So that all being said...Is there any real reason most people hate multiple inheritance, or is it all just a bunch of hysteria that causes more harm than good? Is there something that I am not seeing here? Thank you.

Example

Car extends WheeledVehicle, KIASpectra extends Car and Electronic, KIASpectra contains Radio. Why doesn't KIASpectra contain Electronic?

  1. Because it is an Electronic. Inheritance vs. composition should always be an is-a relationship vs. a has-a relationship.

  2. Because it is an Electronic. There are wires, circuit boards, switches, etc. all up and down that thing.

  3. Because it is an Electronic. If your battery goes dead in the winter, you're in just as much trouble as if all your wheels suddenly went missing.

Why not use interfaces? Take #3, for instance. I don't want to write this over and over again, and I really don't want to create some bizarre proxy helper class to do this either:

private void runOrDont()
{
    if (this.battery)
    {
        if (this.battery.working && this.switchedOn)
        {
            this.run();
            return;
        }
    }
    this.dontRun();
}

(We're not getting into whether that implementation is good or bad.) You can imagine how there may be several of these functions associated with Electronic that are not related to anything in WheeledVehicle, and vice-versa.

I wasn't sure whether to settle down on that example or not, since there is room for interpretation there. You could also think in terms of Plane extending Vehicle and FlyingObject and Bird extending Animal and FlyingObject, or in terms of a much purer example.

Deduplicator
  • 9,209
Panzercrisis
  • 3,213

9 Answers9

80

In many cases, people use inheritance to provide a trait to a class. For example think of a Pegasus. With multiple inheritance you might be tempted to say the Pegasus extends Horse and Bird because you've classified the Bird as an animal with wings.

However, Birds have other traits that Pegasi don't. For example, birds lay eggs, Pegasi have live birth. If inheritance is your only means of passing sharing traits then there's no way to exclude the egg laying trait from the Pegasus.

Some languages have opted to make traits an explicit construct within the language. Other's gently guide you in that direction by removing MI from the language. Either way, I can't think of a single case where I thought "Man I really need MI to do this properly".

Also let's discuss what inheritance REALLY is. When you inherit from a class, you take a dependency on that class, but also you have to support the contracts that class supports, both implicit and explicit.

Take the classic example of a square inheriting from a rectangle. The rectangle exposes a length and width property and also a getPerimeter and getArea method. The square would override length and width so that when one is set the other is set to match getPerimeter and getArea would work the same (2*length+2*width for perimeter and length*width for area).

There is a single test case that breaks if you substitute this implementation of a square for a rectangle.

var rectangle = new Square();
rectangle.length= 5;
rectangle.width= 6;
Assert.AreEqual(30, rectangle.GetArea()); 
//Square returns 36 because setting the width clobbers the length

It's tough enough to get things right with a single inheritance chain. It gets even worse when you add another to the mix.


The pitfalls I mentioned with the Pegasus in MI and the Rectangle/Square relationships are both the results of a inexperienced design for classes. Basically avoiding multiple inheritance is a way to help beginning developers avoid shooting themselves in the foot. Like all design principles, having discipline and training based on them allows you to in time discover when it's okay to break from them. See the Dreyfus Model of Skill Acquisition, at the Expert level, your intrinsic knowledge transcends reliance on maxims/principles. You can "feel" when a rule doesn't apply.

And I do agree that I somewhat cheated with a "real world" example of why MI is frowned upon.

Let's look at a UI framework. Specifically let's look at a few widgets that might at first brush look like they are simply a combination of two others. Like a ComboBox. A ComboBox is a TextBox that has a supporting DropDownList. I.e. I can type in a value, or I can select from a pre-ordained list of values. A naive approach would be to inherit the ComboBox from TextBox and DropDownList.

But your Textbox derives its value from what the user has typed. While the DDL gets its value from what the user selects. Who takes precedent? The DDL might have been designed to verify and reject any input that wasn't in its original list of values. Do we override that logic? That means we have to expose the internal logic for inheritors to override. Or worse, add logic to the base class that is only there in order to support a subclass (violating the Dependency Inversion Principle).

Avoiding MI helps you sidestep this pitfall altogether. And might lead to you extracting common, reusable traits of your UI widgets so that they can be applied as needed. An excellent example of this is the WPF Attached Property which allows a framework element in WPF to provide a property that another framework element can use without inheriting from the parent framework element.

For example a Grid is a layout panel in WPF and it has Column and Row attached properties that specify where a child element should be placed in the grid's arrangement. Without attached properties, if I want to arrange a Button within a Grid, the Button would have to derive from Grid so it could have access to the Column and Row properties.

Developers took this concept further and used attached properties as a way of componentizing behavior (for example here is my post on making a sortable GridView using attached properties written before WPF included a DataGrid). The approach has been recognized as a XAML Design Pattern called Attached Behaviors.

Hopefully this provided a little more insight on why Multiple Inheritance is typically frowned upon.

Deduplicator
  • 9,209
Michael Brown
  • 21,822
62

Is there something that I am not seeing here?

Allowing multiple inheritence makes the rules about function overloads and virtual dispatch decidedly more tricky, as well as the language implementation around object layouts. These impact language designers/implementors quite a bit, and raise the already high bar to get a language done, stable and adopted.

Another common argument I've seen (and made at times) is that by having two+ base classes, your object almost invariably violates the Single Responsibility Principle. Either the two+ base classes are nice self-contained classes with their own responsibility (causing the violation) or they're partial/abstract types that work with each other to make a single cohesive responsibility.

In this other case, you have 3 scenarios:

  1. A knows nothing about B - Great, you could combine the classes because you were lucky.
  2. A knows about B - Why didn't A just inherit from B?
  3. A and B know about each other - Why didn't you just make one class? What benefit comes from making these things so coupled but partially replacable?

Personally, I think multiple inheritance has a bad rap, and that a well done system of trait style composition would be really powerful/useful... but there are a lot of ways that it can be implemented badly, and a lot of reasons it's not a good idea in a language like C++.

[edit] regarding your example, that's absurd. A Kia has electronics. It has an engine. Likewise, it's electronics have a power source, which just happens to be a car battery. Inheritance, let alone multiple inheritance has no place there.

Telastyn
  • 110,259
30

The only reason why it is disallowed is because it makes it easy for people to shoot themselves in the foot.

What usually follows in this sort of a discussion is arguments as to whether the flexibility of having the tools is more important than the safety of not shooting off your foot. There is no decidedly correct answer to that argument, because like most other things in programming, the answer depends on context.

If your developers are comfortable with MI, and MI makes sense in the context of what you are doing, then you will sorely miss it in a language that doesn't support it. At the same time if the team is not comfortable with it, or there is no real need for it and people use it 'just because they can', then that is counter-productive.

But no, there does not exist an all-convincing absolutely true argument that proves multiple inheritance to be a bad idea.

EDIT

Answers to this question appear to be unanimous. For the sake of being the devil's advocate I will provide a good example of multiple inheritance, where not doing it leads to hacks.

Suppose you are designing a capital markets application. You need a data model for securities. Some securities are equity products (stocks, real estate investment trusts, etc) others are debt (bonds, corporate bonds), others are derivatives (options, futures). So if you're avoiding MI, you will make a very clear, simple inheritance tree. A Stock will inherit Equity, Bond will inherit Debt. Great so far, but what about derivatives? They can be based off of Equity-like products or Debit-like products? Ok, I guess we will make our inheritance tree branch out more. Keep in mind, some derivatives are based off of equity products, debt products, or neither. So our inheritance tree is getting complicated. Then along comes the business analyst and tells you that now we support indexed securities (index options, index future options). And these things can be based off of Equity, Debt, or Derivative. This is getting messy! Does my index future option derive Equity->Stock->Option->Index? Why not Equity->Stock->Index->Option? What if one day I find both in my code (This happened; true story)?

The problem here is that these fundamental types can be mixed in any permutation that does not naturally derive one from the other. The objects are defined by an is a relationship, so composition makes no sense whatsoever. Multiple inheritance (or the similar concept of mixins) is the only logical representation here.

The real solution for this problem is to have the Equity, Debt, Derivative, Index types defined and mixed using multiple inheritance to create your data model. This will create objects that both, make sense, and lend easily to code re-use.

MrFox
  • 3,418
11

The other answers here seem to be getting mostly into theory. So here's a concrete Python example, simplified down, that I've actually smashed headlong into, which required a fair amount of refactoring:

class Foo(object):
   def zeta(self):
      print "foozeta"

class Bar(object):
   def zeta(self):
      print "barzeta"

   def barstuff(self):
      print "barstuff"
      self.zeta()

class Bang(Foo, Bar):
   def stuff(self):
      self.zeta()
      print "---"
      self.barstuff()

z = Bang()
z.stuff()

Bar was written assuming it had its own implementation of zeta(), which is generally a pretty good assumption. A subclass should override it as appropriate so that it does the right thing. Unfortunately, the names were only coincidentally the same - they did rather different things, but Bar was now calling Foo's implementation:

foozeta
---
barstuff
foozeta

It is rather frustrating when there are no errors thrown, the application starts acting just ever so slightly wrong, and the code change that caused it (creating Bar.zeta) doesn't seem to be where the problem lies.

Deduplicator
  • 9,209
Izkata
  • 6,118
  • 7
  • 30
  • 44
10

I would argue that there aren't any real problems with MI in the right language. The key is to allow diamond structures, but require that subtypes provide their own override, instead of the compiler picking one of the implementations based on some rule.

I do this in Guava, a language I'm working on. One feature of Guava is that we can invoke a specific supertype's implementation of a method. So it's easy to indicate which supertype implementation should be "inherited", without any special syntax:

type Sequence[+A] {
  String toString() {
    return "[" + ... + "]";
  }
}

type Set[+A] {
  String toString() {
    return "{" + ... + "}";
  }
}

type OrderedSet[+A] extends Sequence[A], Set[A] {
  String toString() {
    // This is Guava's syntax for statically invoking instance methods
    return Set.toString(this);
  }
}

If we didn't give OrderedSet its own toString, we would get a compilation error. No surprises.

I find MI to be particularly useful with collections. For example, I like to use a RandomlyEnumerableSequence type to avoid declaring getEnumerator for arrays, deques, and so forth:

type Enumerable[+A] {
  Source[A] getEnumerator();
}

type Sequence[+A] extends Enumerable[A] {
  A get(Int index);
}

type RandomlyEnumerableSequence[+A] extends Sequence[A] {
  Source[A] getEnumerator() {
    ...
  }
}

type DynamicArray[A] extends MutableStack[A],
                             RandomlyEnumerableSequence[A] {
  // No need to define getEnumerator.
}

If we didn't have MI, we could write a RandomAccessEnumerator for several collections to use, but having to write a brief getEnumerator method still adds boilerplate.

Similarly, MI is useful for inheriting standard implementations of equals, hashCode and toString for collections.

8

Inheritance, multiple or otherwise, is not that important. If two objects of different type are substitutable, that is what matters, even if they are not linked by inheritance.

A linked list and a character string have little in common, and need not be linked by inheritance, but it's useful if I can use a length function to get the number of elements in either one.

Inheritance is a trick to avoid repeated implementation of code. If inheritance saves you work, and multiple inheritance saves you even more work compared to single inheritance, then that's all the justification that is needed.

I suspect that some languages do not implement multiple inheritance very well, and to the practitioners of those languages, that is what multiple inheritance means. Mention multiple inheritance to a C++ programmer, and what comes to mind is something about issues when a class ends up with two copies of a base via two different inheritance paths, and whether to use virtual on a base class, and confusion about how destructors are called, and so on.

In many languages, inheritance of class is conflated with inheritance of symbols. When you derive a class D from a class B, not only are you creating a type relationship, but because these classes also serve as lexical namespaces, you are dealing with the importation of symbols from the B namespace to the D namespace, in addition to the semantics of what is happening with the types B and D themselves. Multiple inheritance therefore brings in issues of symbol clashing. If we inherit from card_deck and graphic, both of which "have" a draw method, what does it mean to draw the resulting object? An object system which doesn't have this problem is the one in Common Lisp. Perhaps not coincidentally, multiple inheritance gets used in Lisp programs.

Badly implemented, inconvenient anything (such as multiple inheritance) should be hated.

Kaz
  • 3,692
1

As far as I can tell, part of the problem (besides making your design a little bit harder to understand (nevertheless easier to code)) is that the compiler is going to save enough space for your class data, allowing this a huge amount of memory waste in the following case:

(My example might not be the best, but try to get the gist about multiple memory space for the same purpose, it was the first thing that came to my mind :P )

Concider a DDD wher the class dog extend from caninus and pet, a caninus has a variable which indicates the amount of food it should eat (an integer) under the name dietKg, but a pet also has another variable for that purpose usually under the same name (unless you set another variable name, then you'll have codify extra code, which was de initial problem that you wanted avoid, to handle and keep the integrity of bouth variables), then you'll have two memory spaces for the exact same purpose, to avoid this you will have to modify your compiler to recognize that name under the same namespace and just assign a single memory space to that data, which unfortunately is umposible to determine in compilation time.

You could, of course, design a languaje to specify that such variable might have already an space defined somewhere else, but at the end the programer should specify where is that memory space which this variable is referencing to (and again extra code).

Trust me the people implementing this thought really hard about all this, but I'm glad you asked, your kind prespective is the one which changes paradigms ;), and concider this, I'm not saying it is impossible (but many asumptions and a multiphased compiler must be implemented, and a really complex one), I'm just saying it does not exists yet, if you start a project for your own compiler capable of doing "this" (multiple inheritance) please let me know, I'll be glad to join to your team.

Ordiel
  • 189
-1

For quite a while now it never really occurred to me how totally different some programming tasks are from others--and how much it helps if the languages and patterns used are tailored to the problem space.

When you are working alone or mostly isolated on code that you wrote it's a completely different problem space from inheriting a codebase from 40 people in India who worked on it for a year before handing it to you without any transitional help.

Imagine you were just hired by your dream company and then inherited such a code base. Furthermore imagine that the consultants had been learning about (and therefore infatuated with) inheritance and multiple-inheritance... Could you picture what you might be working on.

When you inherit code the most important feature is that it is understandable and the pieces are isolated so they can be worked on independently. Sure when you are first writing the code structures like multiple inheritance might save you a little duplication and seem to fit your logical mood at the time, but the next guy just has more stuff to untangle.

Every interconnection in your code also makes it more difficult to understand and modify pieces independently, doubly so with multiple inheritance.

When you are working as part of a team you want to target the simplest possible code that gives you absolutely no redundant logic (That's what DRY really means, not that you shouldn't type much just that you never have to change your code in 2 places to solve a problem!)

There are simpler ways to achieve DRY code than Multiple Inheritance, so including it in a language can only open you up to problems inserted by others who might not be at the same level of understanding as you are. It's only even tempting if your language is incapable of offering you a simple/less complex way to keep your code DRY.

Bill K
  • 2,749
-3

The biggest argument against multiple inheritance is that some useful abilities can be provided, and some useful axioms can hold, in a framework which severely restricts it(*), but cannot cannot be provided and/or hold without such restrictions. Among them:

  • The ability to have multiple separately-compiled modules include classes which inherit from other module's classes, and recompile a module containing a base class without having to recompile every module that inherits from that base class.

  • The ability for a type to inherit a member implementation from a parent class without the derived type having to re-implement it

  • The axiom that any object instance may be upcast or downcast directly to itself or any of its base types, and such upcasts and downcasts, in any combination or sequence, are always identity-preserving

  • The axiom that if a derived class overrides and chains to a base-class member, the base member will be invoked directly by the chaining code, and will not be invoked anywhere else.

(*) Typically by requiring that types supporting multiple inheritance be declared as "interfaces" rather than classes, and not allowing interfaces to do everything normal classes can do.

If one wishes to allow generalized multiple inheritance, something else must give way. If X and Y both inherit from B, they both override the same member M and chain to the base implementation, and if D inherits from X and Y but does not override M, then given an instance q of type D, what should ((B)q).M() do? Disallowing such a cast would violate the axiom that says any object can be upcast to any base type, but any possible behavior of the cast and member invocation would violate the axiom regarding method chaining. One could require that classes only be loaded in combination with the particular version of a base class they were compiled against, but that is often awkward. Having the runtime refuse to load any type in which any ancestor can be reached by more than one route might be workable, but would greatly limit the usefulness of multiple inheritance. Allowing shared inheritance paths only when no conflicts exist would create situations where an old version of X would be compatible with an old or new Y, and an old Y would be compatible with an old or new X, but the new X and new Y would be compatible, even if neither did anything which in and of itself should be expected to be a breaking change.

Some languages and frameworks do allow multiple inheritance, on the theory that what's gained from MI is more important than what must be given up to allow it. The costs of MI are significant, however, and for many cases interfaces provide 90% of the benefits of MI at a small fraction of the cost.

supercat
  • 8,629