152

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
  • 8,591
  • 5
  • 31
  • 50
Panzercrisis
  • 3,145
  • 4
  • 19
  • 34
  • 26
    it also encourages inheritance over composition... (when it should be the other way around) – ratchet freak Nov 14 '13 at 16:02
  • 38
    "You can screw it up" is a perfectly valid reason to remove a feature. Modern language design is somewhat splintered on that point, but you can generally classify languages into "Power from restriction" and "Power from flexibility". Neither of which is "correct", I don't think, they both have strong points to make. MI is one of those things which is used for Evil more often than not, and thus restrictive languages remove it. Flexible ones don't, because "more often than not" isn't "literally always". That said, mixins/traits are a better solution for the general case, I think. – Phoshi Nov 14 '13 at 16:08
  • 10
    There are safer alternatives to multiple inheritance in several languages, that go beyond interfaces. Check out Scala's `Traits` - they act like interfaces with optional implementation, but have some restrictions that help prevent issues like the diamond problem from arising. – KChaloux Nov 14 '13 at 16:19
  • Regarding Traits, if you're interested: http://blog.safaribooksonline.com/2013/05/30/traits-how-scala-tames-multiple-inheritance/ – KChaloux Nov 14 '13 at 16:27
  • I remember back in the day of VC++ 1.0, I was reading about MS design choices in MFC and how they said they will not use multiple inheritance. For all the reasons stated here... complexity, usually there are better ways... etc. I developed such a habit with my own C++ code. Although once in a while (rarely) I might add a "helper" method to what otherwise would be an interface, from personal coding experience I did find that all of my designs, no matter the scale, worked perfectly well with single inheritance. All I can suggest is try both for yourself and make your own call. – DXM Nov 14 '13 at 16:32
  • I honestly just thought it was because it made it harder to make the language itself. The people who are making the thing are the people who make the decisions, so it seems like they did it because what you get for the work wasn't perceived as worth the effort it took to make and maintain it as part of the language. – BrianH Nov 14 '13 at 16:32
  • Side note: I have yet to decide whether C# with interfaces and extension methods can "really" serve the same role as traits, but the behavior is certainly very possible there (See all of the function side of LINQ). I think it ends up being mostly Java as the great big OO language with no answer to this problem. – Phoshi Nov 14 '13 at 16:37
  • 5
    See also this formerly closed question: http://programmers.stackexchange.com/questions/122480/what-are-the-pros-and-cons-of-multiple-inheritance?rq=1 – Doc Brown Nov 14 '13 at 17:09
  • 19
    `KiaSpectra` isn't _an_ `Electronic`; it _has_ Electronics, and may be an `ElectronicCar` (which would extend `Car`...) – Brian S Nov 14 '13 at 19:01
  • 5
    I suggest that your recent edit also has design issues. A plane "is a" vehicle which "has" features of a FlyingObject. For example, a plane and a bird both fly, but have different implementations (engine vs wings) - thus FlyingObject should be an interface. – kwah Nov 14 '13 at 19:14
  • "diamond problem" almost seems like an euphemism for the problems that may arise. worst case, you get a "knitting pattern problem" – Cephalopod Nov 14 '13 at 21:56
  • 2
    It is repeated inheritance that can be bad (where indirectly inherit the same class more than once), C++ did not implement repeated inheritance well. So all the derived languages through the baby out with the bath water, and do not have multiple inheritance. All the stuff about inheritance vs composition, or over use of inheritance is a retrospective bogus view point, that if true would lead to a lot of other features being removed. See Eiffel language for an example of how to do multiple inheritance well. – ctrl-alt-delor Nov 15 '13 at 09:44
  • 3
    "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?" - nice [false dilemma](http://en.wikipedia.org/wiki/False_dilemma) you've got there... oh and I suspect there's a [true Scotsman](http://en.wikipedia.org/wiki/No_true_Scotsman) hiding behind that "real" too. – AakashM Nov 15 '13 at 13:35
  • 2
    @AakashM I think I see what you're saying, but that's not quite the purpose or the really even message behind this question. What I was unable to accept, without more reasons, more elaboration on the one about the diamond problem, or much truer substitutes than just watered-down interfaces is why other people seem to be wanting to make a false dilemma out of this, generally wanting to outright prevent a sometimes useful programming practice in language after language after language. – Panzercrisis Nov 15 '13 at 14:05
  • @Panzercrisis: The problem I noticed (and the reason for my close vote) was that your question attracted a large number of answers that all went in different directions, giving what appeared for most to be no more than personal opinion. – Bart van Ingen Schenau Nov 23 '13 at 16:49
  • 1
    I have a code base in c++ where ***composition is almost never used***, and I can tell you dealing with an inheritance tree which is a f****** forest is a nightmare. – UmNyobe Jan 31 '14 at 10:01
  • I wouldn't use it in most cases, but I like the idea of it at least partially being available. Partially going back up another comment though, the answer I accepted showed a clear, technical reason to not allow this in most languages. I didn't know about traits and mixins before, and they evidently do most of the same good with limited negative effects. – Panzercrisis Jan 31 '14 at 13:56
  • CloseOverflow stroke again :-( There is nice point in "Programming Language Pragmatics" that you pay the price of indirect call in every call no matter if you use MI or not. The authors notes there are implementations without such penalty and also points out this is just one of the problem and not most severe but does not go into more details (I hate such premises without full explanations). And as MI is problematic (to implement, and to track the dependencies), pure SI is not a solution because it violates another principle -- DRY. – greenoldman Mar 15 '14 at 16:05
  • Generally, multiple inheritance causes tricky context dependent issues, as well as violating lots of design principles. I have yet to see a MI problem which wouldn't be solved by the Properties Pattern: http://steve-yegge.blogspot.ca/2008/10/universal-design-pattern.html – Zeroth Mar 17 '14 at 20:17
  • I think I'm the only one who thinks MI is composition that the compiler does for you. Let's say you have a player in a video game, he can inherit from Sprite, RigidBody, Controller, 2DCollider. Now some people make the mistake saying 'hey I want a NPC Player' so I'll just inherit from Player when they really should have just inherited from Sprite, Rigidbody, AI, 2dCollider. Then again, the only real benefit you get is that you don't have to write any 'forwarder' methods to the components, but it does help keep your code DRY by not having to write those forwarder methods. – Isaac Paul Feb 10 '15 at 05:44
  • You say "I really don't want to create some bizarre proxy helper class", but I think that's actually the better approach. There's so many times I resist making a separate class for something, but when I finally do I realize there's more code that can live there than I realized, and it ends up making things so much more organized than I would have thought. – Dave Cousineau Aug 29 '19 at 22:04
  • 1
    If you don't like writing boilerplate code, I think Resharper templates (or some other similiar feature from some other IDE) is a good solution. It allows you to generate boilerplate code with parameterized values so that you can write a type name once that appears multiple times, for example. – Dave Cousineau Aug 29 '19 at 22:04
  • This [question](https://softwareengineering.stackexchange.com/questions/435571/design-classes-to-model-3d-scanned-faces-of-ancient-greek-roman-sculptures-is-m) might be useful to deepen this topic. – blunova Dec 27 '21 at 13:54

9 Answers9

79

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
  • 8,591
  • 5
  • 31
  • 50
Michael Brown
  • 21,684
  • 3
  • 46
  • 83
  • 15
    "Man I really need MI to do this properly" - see my answer for an exmaple. In a nutshell, orthogonal concepts that satisfy an is-a relationship make sense for MI. They are very rare, but they exist. – MrFox Nov 14 '13 at 19:38
  • 3
    I was unaware of traits and mixins until a couple of hours ago (I had to take some time and go read up on them a little), so your answer was real informative. They actually seem like a pretty good proxy. Thanks. – Panzercrisis Nov 14 '13 at 20:35
  • Plus I guess interfaces would help with is keywords and stuff like that. – Panzercrisis Nov 14 '13 at 20:56
  • 14
    Man, I hate that square rectangle example. There are plenty of more illuminating examples that don't require mutability. – asmeurer Nov 15 '13 at 00:09
  • 12
    I love that the answer to "provide a real example" was to discuss a fictional creature. Excellent irony +1 – jjathman Nov 15 '13 at 02:04
  • 1
    "If inheritance is your only means of passing sharing traits then there's no way to exclude the egg laying trait from the Pegasus." Many non-MI languages provide an "Aspect Oriented" approach to codifying behavioral differences. Sometimes a subclass only differs in attribution as superclasses provide all the core behavior required, and in cases where the superclass does not provide necessary behavior the subclass appropriately provides new implementation code, yet in other cases inheritance isn't required at all to elicit different behavior. An example of this would be serialization in .NET – Shaun Wilson Nov 15 '13 at 09:36
  • 4
    So you are saying that multiple inheritance is bad, because inheritance is bad (you examples are all about single interface inheritance). Therefore, based on this answer alone, a language should either be rid of inheritance and interfaces, or have multiple inheritance. – ctrl-alt-delor Nov 15 '13 at 09:50
  • 15
    I don't think these examples really work...no one says a pegasus is a bird and a horse...you say it's a WingedAnimal and a HoofedAnimal. The square and rectangle example makes a little more sense because you find yourself with an object that behaves differently than you would think based on its asserted definition. However, I think this situation would be the programmers fault for not catching this (if indeed this did prove to be a problem). – richard Nov 16 '13 at 05:01
  • 1
    Although the answer is well written, the example given is flawed, because Pegasus is not a full Bird type (https://en.wikipedia.org/wiki/Liskov_substitution_principle). But it's still inspiring in that it does raises the question "what's inheritance". – ribamar Mar 20 '18 at 17:20
  • @MrFox: Orthogonal concepts, however, would be perfectly doable via interfaces. As per OP's question: `Interfaces simply do not cover all the same ground multiple inheritance does`. I can't think of anything that isn't covered by interfaces still two orthogonal concepts. – Flater May 31 '18 at 14:39
61

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
  • 108,850
  • 29
  • 239
  • 365
  • 9
    Another interesting question is how do base classes + their base classes get initialized. Encapsulation > Inheritance. – daniel gratzer Nov 14 '13 at 16:41
  • *"a well done system of trait style composition would be really powerful/useful..."* - These are known as [mixins](http://en.wikipedia.org/wiki/Mixin), and they *are* powerful/useful. Mixins can be implemented using MI, but **Multiple Inheritance is not required for mixins.** [Some languages](http://en.wikipedia.org/wiki/Ruby_%28programming_language%29) support mixins inherently, without MI. – BlueRaja - Danny Pflughoeft Nov 14 '13 at 20:21
  • For Java in particular, we already have multiple interfaces and INVOKEINTERFACE, so MI wouldn't have any obvious performance implications. – Daniel Lubarov Nov 15 '13 at 05:10
  • Tetastyn, I agree that the example was bad and didn't serve his question. Inheritance isn't for adjectives (electronic), it's for nouns (vehicle). – richard Nov 16 '13 at 05:05
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,398
  • 2
  • 19
  • 23
  • 30
    I've worked in finance. There's a reason why functional programming is preferred over O-O in financial software. And you've pointed it out. MI doesn't fix this problem it exacerbates it. Believe me I can't tell you how many times I've heard "It's just like this...except" when dealing with finance clients That except becomes the bane of my existence. – Michael Brown Nov 14 '13 at 20:02
  • 3
    @MikeBrown I agree with you about functional programming. The problem really rears its ugly head though when for compatibility reasons you are stuck with using Java. You have to do OO, but you can't do MI. I literally saw the problem I outlined here multiple times in my life in different financial institutions. There is just no good way of doing this in Java. – MrFox Nov 14 '13 at 20:09
  • 9
    This seems very solvable with interfaces to me... in fact, it seems *better* suited to interfaces than anything else. `Equity` and `Debt` both implement `ISecurity`. `Derivative` has an `ISecurity` property. It may itself be an `ISecurity` if appropriate (I don't know finance). `IndexedSecurities` again contain an interface property which gets applied to the types it's allowed to be based off of. If they're all `ISecurity`, then they all have `ISecurity` properties and can be arbitrarily nested... – Bobson Nov 14 '13 at 20:30
  • 11
    @Bobson the problem comes in that you have to re-implement the interface for each implementor, and in many cases it IS just the same. You can use delegation/composition but then you lose access to private members. And since Java doesn't have delegates/lambdas...there is literally no good way to do it. Except possibly with an Aspect tool like Aspect4J – Michael Brown Nov 14 '13 at 20:45
  • 10
    @Bobson exactly what Mike Brown said. Yes, you can design a solution with interfaces, but it will be clunky. Your intuition to use interfaces is very correct though, it is a hidden desire for mixins/multiple inheritance :). – MrFox Nov 14 '13 at 20:56
  • 1
    If it's going to be the same in most cases, why not have a base `Security` type that everything inherits from and implement them there? I feel like this is a very solvable problem, although I'm sure that if it's been tried so many times then it's much harder than it appears on the surface. – Bobson Nov 14 '13 at 21:19
  • 1
    @MikeBrown: Delegates/lambdas are just single-method classes. Just have Equity, Debt, and Derivative return an ISecurity instance on request. If the code is similar, you can use the same base class to implement an ISecurity instance. In Java it's easy for a class to create an object of an anonymous class (the anon class extending a named class with generally useful code), and then use that object as its own representative to the rest of the system. – RalphChapin Nov 14 '13 at 21:34
  • 13
    This touches on an oddity where OO programmers prefer to think in terms of inheritance and oft fail to accept delegation as a valid OO approach, one which typically yields a leaner, more maintainable and extensible solution. Having worked in finance and healthcare I've seen the unmanageable mess MI can create, especially as tax and health laws change year over year and a definition of an object LAST year is invalid for THIS year (yet must still perform the function of either year, and must be interchangeable). Composition yields leaner code that is easier to test, and it costs less over time. – Shaun Wilson Nov 15 '13 at 09:53
  • 2
    I agree with Shaun Wilson here. Inheritance is simply a crutch that many programmers have come to rely on, when other relationships are better. I don't find it to be a natural at all to model that via MI, personally. Delegation is vastly better here, and woefully misunderstood. – WolfgangSenff Nov 15 '13 at 20:14
  • @MikeBrown: If a group of classes share most, but not all, functionality then it is trivial to make arbitrarily nested abstract implementations, each of which may be open to being overridden when necessary. No need for interface soup, no need for mixed inheritance, while still minimizing duplication. – Andrew Coonce Nov 15 '13 at 21:55
  • 2
    @ShaunWilson the situation I'm describing in the answer is one that will never change. It is absolutely always correct that an EquityIndexOption will have properties of Equity, of an Index, and those of an Option. It **is** conceptually all of those 3, and it hasn't changed since this security type was invented. Functions that operate on Options should be able to accept it. Please note, I agree with your statement in general, but I reject your claim that it is always the case. Composition does not always lead to learner, better code. It may do that most of the time, but not always! – MrFox Nov 19 '13 at 14:30
  • 1
    The fact that a feature can be misused is not a good reason to remove it (unless maybe you're designing one of those beginner languages where you don't want to burden the poor kiddies with actually having to learn anything from their own mistakes.) A good reason to leave out a feature is if the feature adds _unnecessary_ complication. Multiple inheritance is significantly harder to implement than single inheritance, and the rules are harder to explain, _and_ it doesn't let you solve any problem that can't be solved with delegation, composition, and/or other patterns. – Solomon Slow Jun 22 '16 at 17:51
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
  • 8,591
  • 5
  • 31
  • 50
Izkata
  • 6,048
  • 6
  • 28
  • 43
  • How is the Diamond problem avoided through calls to `super()`? – Panzercrisis Nov 14 '13 at 20:16
  • @Panzercrisis Sorry, nevermind that part. I misremembered which problem the "diamond problem" usually refers to - Python had a separate issue caused by diamond-shaped inheritance that `super()` got around – Izkata Nov 14 '13 at 20:19
  • 6
    I'm glad C++'s MI is much better designed. The above bug is simply not possible. You have to manually disambiguate it before the code will even compile. – Thomas Eding Nov 17 '13 at 04:16
  • 2
    Changing the order of inheritance in `Bang` to `Bar, Foo` would also fix the problem - but your point is a good one, +1. – Sean Vieira Nov 19 '13 at 03:34
  • @ThomasEding, jokes aside, this is not a bug in Python. It is a feature. `self` always refers to the instance as the most specific class used (read: farthest down the inheritance tree) regardless of which class contains the method being called. There are even two terms for the order in which members are resolved: C3 linearization and method resolution order (MRO). See https://www.python.org/download/releases/2.3/mro/ for more information. – Tyler Crompton Aug 05 '14 at 23:45
  • 2
    @Tyler: I still consider that "feature" a bug. Aka a "beature". Why would I want a compiler to automatically decide the right choice for me in the general case when manually disambiguating can never go wrong? – Thomas Eding Aug 06 '14 at 05:03
  • 2
    @ThomasEding You can manually disambiguate still: `Bar.zeta(self)`. – Tyler Crompton Aug 07 '14 at 21:43
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.

Daniel Lubarov
  • 1,226
  • 8
  • 12
  • Sounds pretty good, though it sounds as though allowing MI would mean that for a base type to add an override of an inherited member would be a breaking change, whereas without MI it wouldn't have to be. – supercat Nov 15 '13 at 22:55
  • Something smells funny here. By allowing the end-user of the class to decide how a particular instance will behave is't some fundamental tenet of 'is a' being violated? I mena if the instance 'is a' zebra,but depending on the circumstances it 'is a' animal, **why not simply model an appropriate inheritance hierarchy?** How can the instance be both an instance of animal, zebra, and horse, two of which are contrdictory and cause MI conflict. Just because at runtime you resolve the dispute based on information tha user of the class hierachy has about the 'is a' of the instance you're – Andyz Smith Nov 17 '13 at 06:11
  • breaking some kind of tenet of the characteristics of the object defining it's type. – Andyz Smith Nov 17 '13 at 06:12
  • 1
    @AndyzSmith, I see your point, but not all inheritance conflicts are actual semantic problems. Consider my example - none of the types make promises about `toString`'s behavior, so overriding its behavior doesn't violate the substitution principle. You also sometimes get "conflicts" between methods which have the same behavior but different algorithms, especially with collections. – Daniel Lubarov Nov 17 '13 at 07:37
  • Of course not, but langauges that make it easy to merge two sepapate class hierarchies must be used be used by expert coders. Without offering any discipline and compile time structure/errors regarding the inheritance intentions of the design, they suffer from a high risk that the designs they facilitate will be highly brittle and impossible to merge and will not be able reconcile ambiguous method resolution. – Andyz Smith Nov 17 '13 at 15:40
  • Hmm. Hope I don't come off weird saying this, but your answer has "completely" convinced me against MI - your examples are good, but the thing is, you're using it for functionality, not identity - and that's what mixins are for, and they are much better at it. – Llamageddon Oct 05 '14 at 19:36
  • 1
    @Asmageddon agreed, my examples involve functionality only. What do you see as the advantages of mixins? At least in Scala, traits are essentially just abstract classes which permit MI - they can still be used for identity and still bring about the diamond problem. Are there other languages where mixins have more interesting differences? – Daniel Lubarov Oct 05 '14 at 21:30
  • 1
    Technically abstract classes can be used as interfaces, to me, the benefit simply the fact of the construct itself being an "usage tip", meaning I know what to expect when I see the code. That said, I am currently coding in Lua, which has no inherent OOP model - existing class systems commonly allow including tables as mixins, and the one I'm coding uses classes as mixins. The only difference is that you can only use (single)inheritance for identity, mixins for functionality(with no identity info), and interfaces for further checks. Maybe it is not somehow way better, but it makes sense to me. – Llamageddon Oct 06 '14 at 20:01
  • 2
    Why do you call the `Ordered extends Set and Sequence` a `Diamond`? It is just a Join. It lacks the `hat` vertex. Why do you call it a diamond? I asked [here](http://programmers.stackexchange.com/questions/289742/why-diamond-is-a-problem-rather-than-join?noredirect=1#comment599354_289742) but it seems a taboo question. How did you know that you need to call this triangualar structure a Diamond rather than Join? – Val Jul 14 '15 at 18:25
  • 2
    @RecognizeEvilasWaste that language has an implicit `Top` type which declares `toString`, so there was actually a diamond structure. But I think you have a point - a "join" without a diamond structure creates a similar problem, and most languages handle both cases the same way. – Daniel Lubarov Jul 14 '15 at 21:20
  • Good answer. I think that it could also answer my question. Since most of the languages ascend to some root class, we can roughly say that you cannot have multiple inheritance without Diamond. That is why MI is reduced to diamond. But, it still should be wrong for rare languages without common parent and, moreover, for the interfaces, which never have it. I had an example in my question where declaring getName in two clashing interfaces had some sense (I don't remember why I didn't use `toString` there). – Val Jul 14 '15 at 21:35
7

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,572
  • 1
  • 19
  • 30
  • 2
    Your example with length() method of list and string containers is bad, since these two are doing completely different things. The purpose of inheritance is not to reduce the code repetition. If you want to reduce code repetition, you refactor common parts into a new class. – BЈовић Nov 15 '13 at 07:07
  • 2
    @BЈовић The two are not doing "completely" different things at all. – Kaz Nov 15 '13 at 07:09
  • 1
    Comparting [string](http://en.cppreference.com/w/cpp/string/basic_string) and [list](http://en.cppreference.com/w/cpp/container/list), one can see lots of repetition. Using inheritance to implement these common methods would simply be wrong. – BЈовић Nov 15 '13 at 07:17
  • 2
    @BЈовић It isn't said in the answer that a string and list should be linked by inheritance; quite the opposite. The behavior of being able to substitute a list or a string in a `length` operation is useful independently of the concept of inheritance (which in this case doesn't help: we are not likely to be able to achieve anything by trying to share the implementation between the two methods of the `length` function). There could nevertheless be some abstract inheritance: e.g. both list and string are of type sequence (but which doesn't provide any implementation). – Kaz Nov 15 '13 at 08:43
  • 2
    Also, inheritance **is** a way of refactoring parts to a common class: namely the base class. – Kaz Aug 05 '14 at 18:51
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
  • 7
  • 1
    At what point could a compiler ever assume that variables of the same name from different parent classes are safe to combine? What guarantee do you have that caninus.diet and pet.diet actually serve the same purpose? What would you do with function/methods with the same name? – Mr.Mindor Nov 14 '13 at 23:03
  • hahaha I'm totally agree with you @Mr.Mindor that's exactly what I'm saying in my answer, the only way that comes to my mind to get near to that aproach is prototype orientated programming, but I'm not saying it's impossible, and that's exactly the reason why I said that that many asumptions must be made during compilation time, and some extra "data"/especifications must be written by teh programmer (nevertheless it is more coding, which was the original problem) – Ordiel Nov 15 '13 at 14:59
-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,699
  • 18
  • 18
  • *Furthermore imagine that the consultants had been learning about* - I've seen so many non-MI languages (eg .net) where the consultants went crazy with interface-based DI and IoC to make the whole thing an impenetrably convoluted mess. MI doesn't make this worse, probably makes it better. – gbjbaanb Dec 03 '15 at 11:23
  • @gbjbaanb You are right, it is easy for someone who hasn't been burnt to mess up any feature--in fact it's almost a requirement for learning a feature that you mess it up in order to see how to best use it. I'm kind of curious because you seem to be saying that not having MI forces more DI/IoC which I haven't seen--I wouldn't think that the consultant code you got with all the crappy interfaces/DI would have been better also having a whole crazy many-to-many inheritance tree, but it could be my inexperience with MI. Do you have a page I could read on replacing DI/IoC with MI? – Bill K Dec 03 '15 at 17:32
-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,335
  • 22
  • 28
  • Would down-voters claim to comment? I believe that my answer provides information not present in other languages, with regard to semantic advantages that can be received by disallowing multiple inheritance. The fact that allowing multiple inheritance would require giving up other useful things would be a good reason for anyone who values those other things more than multiple inheritance to oppose MI. – supercat Aug 03 '14 at 18:54
  • 2
    The problems with MI are easily solved or avoided altogether using the same techniques you'd use with single inheritance. None of the abilities/axioms you've mentioned is inherently hampered by MI except for the second one...and if you're inheriting from two classes that provide different behaviors for the same method, it's on you to resolve that ambiguity anyway. But if you find yourself having to do that, you're probably already misusing inheritance. – cHao May 26 '15 at 15:07
  • @cHao: If one requires that derived classes must override parent methods *and not chain to them* (same rule as interfaces) one can avoid the problems, but that's a pretty severe limitation. If one uses a pattern where `FirstDerivedFoo`'s override of `Bar` does nothing but chain to a protected non-virtual `FirstDerivedFoo_Bar`, and `SecondDerivedFoo`'s override chains to protected non-virtual `SecondDerivedBar`, and if code that wants to access base method uses those protected non-virtual methods, that might be a workable approach... – supercat May 26 '15 at 15:47
  • ...(eschewing the syntax `base.VirtualMethod`) but I don't know of any language support to particularly facilitate such an approach. I wish there were a clean way to attach one block of code to both a non-virtual protected method and a virtual method with the same signature without requiring a "one-liner" virtual method (which ends up taking a lot more than one line in source code). – supercat May 26 '15 at 15:52
  • There's no inherent requirement in MI that classes not call base methods, and no particular reason one would be needed. It seems a bit odd to blame MI, considering that problem also affects SI. – cHao May 26 '15 at 17:56
  • @cHao: In an MI language, if `Foo` derives from `Moo` and `Goo`, each of which overrides a common inherited method `Zoo` in its own way, unless one inherited class is designated "primary", I'm not sure what `base.Zoo` could mean. Having a means for the derived class to invoke `Moo`'s override of `Zoo` or `Goo`'s override of `Zoo` would be fine, and one could define a syntax `((typeName)base).member` which was only valid when `typeName` was either the type of the enclosing class or an *immediate* parent thereof, which would bypass any overrides by classes derived from it, but... – supercat May 26 '15 at 18:05
  • ...that would be very different semantics from a `((typeName)anythingElse)` cast. – supercat May 26 '15 at 18:06
  • And that is exactly `Foo` must resolve the ambiguity between `Moo::Zoo` and `Goo::Zoo`: it's the only class capable of doing so. If there's no way to do that, then inheritance is not the right relationship. MI is less creating the problem than highlighting it as an actual problem. – cHao May 27 '15 at 06:59
  • @cHao: If `Moo:Zoo` casts `this` to `Object`, should the result be the same as if `Goo:Zoo` does so, or if a `Foo` is cast directly? I don't think C++ would allow a direct cast from `Foo` to a multiply-inherited base type, but identity-preserving upcasts and downcasts are an essential and useful feature of Java and .NET. – supercat May 27 '15 at 15:04
  • Casting in itself is just another hack to make strict static typing palatable. So i don't particularly care whether a cast is "identity preserving", because it's something that really ought to be avoided anyway. But the problem of switching from one type to another was already largely solved by the likes of COM, so there's no reason .net couldn't have done it as well. It already had to be done in order to make interfaces work in the first place. – cHao May 27 '15 at 17:26
  • @cHao: There are advantages to having a "reference to anything" type to which any reference may be cast in identity-preserving fashion. I think COM "solved" the problem by limiting its "inheritance" to the .NET interface pattern: don't allow a type to expose fields and require all methods be re-implemented by any implementing class. Does COM offer some other form of inheritance (multi- or otherwise) I don't know about? – supercat May 27 '15 at 17:34
  • COM solved the problem by letting you ask the object itself for an interface that lets you manipulate it in a chosen manner. That "interface" may in fact be a full-fledged class, but you intentionally don't know or care about that. As for a "reference to anything" type, no, it's actually rather useless since generics. But you can have such a baseline type even with MI. – cHao May 27 '15 at 17:51