Where Multiple Levels Of Inheritance Will Kill You

« »

One of the best features of PHP’s object model (and really all object models) is the concept of inheritance – that is, derived classes inherit the members and methods of their parents. This is a fantastic way to further encapsulate and abstract your code because it means you can define some base functionality and then later on extend that class to add new functionality and even override existing functionality to make the class specific.

But this concept is a double-edged sword in PHP (and all other languages). Here’s where multiple inheritances can kill you.

Imagine that your source code has the following…

<?php

abstract class BaseClass
{
    // Some code here.
}

abstract class AddSomeFunctionality extends BaseClass
{
    // More basic functions.
}

class DoSomeWork extends AddSomeFunctionality
{
    // Actual work done.
}

class DoSomeMoreWork extends DoSomeWork
{
    // More work done; override some of the methods in prior classes.
}
&#91;/sourcecode&#93;

On its face this might not look too terribly complex. It's not until you actually get into the code that you start to have some problems. Imagine the following function:

&#91;sourcecode language="php"&#93;
<?php

function Execute(DoSomeWork $object)
{
    $object->runMyFunc();
}

$object = new DoSomeMoreWork();
Execute($object);

What happens here? Does this work? Absolutely; the way inheritance works is that every class that is part of the family is considered an “instanceof” for our purposes. But imagine that runMyFunc() had been defined in AddSomeFunctionality and then overridden in DoSomeMoreWork. You’re going to get unpredictable behavior by passing it an object of DoSomeMoreWork, even though it passes the typehinting check.

This is certainly a problem that can be solved through wise coding and understanding how inheritance works. It’s surely true that you could institute a simple check using instanceof; however, if you ever some day extend DoSomeWork with a new class (say called DoMyWork) then the check will fail just as it does above.

The better solution is to be cautious with inheritance. Developers owe it to themselves and to their peers to master the concepts of “is-a” versus “in a”. Objects that are of the same family should extend one another; objects that are not part of the same family should be given to one another but should not extend one another.

An example:

<?php

abstract class Fruit {}

abstract class TreeFruit extends Fruit {}

abstract class SummerFruit extends TreeFruit {}

class Cherry extends SummerFruit {}

/* Now for a class that shouldn't be in the family */
class FruitSalad extends Cherry {}

&#91;/sourcecode&#93;

In this example, we have fruit. We extend that into tree fruits (cherries, peaches, pears, etc.) and furthermore we extend it into summer fruits. From summer fruits we could go any number of directions; I chose cherries (because I love cherries). But we could also have a class of peaches, plums, etc. that would classify as summer tree fruits. But as soon as we extend cherry to fruit salad, we create a problem. This is where inheritance can kill you. A fruit salad is not a fruit (our base class); a fruit salad is composed of fruits. Let's take a look at a better object model.

&#91;sourcecode language="php"&#93;
<?php

/** Assume that all classes not redefined here exist from our last example **/

final class Cherry extends SummerFruit {}

class FruitSalad
{
    protected $_fruits;

    public function addFruit(Fruit $fruit)
    {
        if(!is_array($this->_fruits))
            $this->fruits = array();
        
        return $this->_fruits[] = $fruit;
}

This is a much better object model; instead of trying to extend the Cherry class we can have a full fruit salad (composed of any other fruits we might define). FruitSalad may not inherit the methods of Cherry, but it will be able to access the Cherry API (the public methods).

Inheritance is a glorious and useful tool, but used improperly it creates terrible problems. Good design, smart architecture, and understanding what is a member of the family and what is not will help ensure that the programmers who come after you will be more easily able to understand your code.

Brandon Savage is the author of Mastering Object Oriented PHP and Practical Design Patterns in PHP

Posted on 7/17/2009 at 8:00 am
Categories: PHP 5, Best Practices, System Architecture
Tags: , , ,

fqqdk (@fqqdk) wrote at 7/17/2009 9:03 am:

Hi!

I would just like to point out that your article has a misleading title. “Multiple inheritance” does not denote multiple levels in the inheritance hierarchy. It means that classes can have multiple superclasses. You can do that in languages like C++, but not in PHP (if we don’t count interfaces; a class can implement multiple interfaces)
Please don’t confuse your readers, and update the title of the article to something like “where multiple levels of inheritance kills you”.

Duane Gran (@duanegran) wrote at 7/17/2009 9:10 am:

Indeed, this illustrates the danger of poorly designed is-a relationships but multiple inheritance is fortunately not a problem found often in PHP. The more apt term for it is polymorphism and there are few real world cases where the design complexity warrants using it.

Avi Block wrote at 7/17/2009 9:23 am:

This of course is a really bad use case for inheritance. It is actually very hard to get inheritance done correctly, and in most cases, a composition type pattern would work better. For adding functionality, nothing beats the decorator pattern.

Suter (@msuterski) wrote at 7/17/2009 9:38 am:

Hi Brandon,

Isn’t it that the main problem with your example is that you extend from the wrong base classes for the beginning?

fruit->tree->fruit->fruit?

Why would you start with fruit, then the tree, and than back to fruits again? As you said, you mixed 2 families of classes. So the problem is not with “scary”
inheritance. It’s a bad design. The logic is wrong.

So, I don’t quite understand what are you trying to say in the post.

Don’t scare people from inheritance. Inheritance doesn’t kill as long as you know what you’re doing. And obviously you don’t in this post.

martin wrote at 7/17/2009 9:39 am:

Apart from the fact that i, like fqqdk, fell for “multiple levels of inheritance” meaning multiple inheritance being bad, i don’t get your point at all. What exactly is so special about PHP here that is not possible in other languages?

And the DoSomeMoreWork example is exactly what OO inheritance is made for. You override methods in subclasses so the specialities of the subclass can be taken care of. I.e. if you can “prepare” a fruit, you might want to “peel” a banana, but not a cherry. A fruit salad simply is not a fruit, therefore the example is pointless in any programming language

Brandon Savage (@brandonsavage) wrote at 7/17/2009 9:49 am:

Suter, I’m sorry you missed the point. Cherries grow on trees, which makes them a variety of fruit called a tree fruit. And I think that in your case, you might want to apply the golden rule when commenting on other people’s work.

Martin, it is what inheritance is made for – if you intend for that to happen. For example, if you direct the function to return a stdClass object and you override that telling it to return an array, you might get undesired functionality.

As for extending Cherry with FruitSalad, you’d be amazed at how often I see that very mistake. Which is why I used that perhaps overly-simplified mistake – everyone can relate to the fact that a fruit salad is not made of a single fruit. On the other hand they might not be able to see that their database object isn’t in the same family as their business logic. Hopefully they’ll think about the example and give it some thought.

Mathias (@mathiasverraes) wrote at 7/17/2009 10:31 am:

The problems you described can be solved using well known principles in OOP theory:
1/ Favor composition over inheritance
2/ Program to interfaces, not implementations
3/ Encapsulates what varies
Other people can explain these better than I ever could, so just google it

Suter (@msuterski) wrote at 7/17/2009 10:31 am:

Brandon,

Yep, you’re right. I misread that class name. My bad.

But that doesn’t change my point of not scaring people from inheritance. If used properly, it doesn’t harm. And if you know what you’re doing it won’t kill anyone.

Brandon Savage (@brandonsavage) wrote at 7/17/2009 10:37 am:

Suter,

Point taken. I don’t want to scare people away from inheritance. I want them to use it properly. Sadly, the examples I provided were dumbed-down versions of examples I’ve seen in real life code, including some major frameworks and applications. Good architecture is essential.

gasper_k (@gasper_k) wrote at 7/17/2009 10:42 am:

I don’t think your first example causes unpredictable behavior in any way. Your Execute function demands an instance of DoSomeWork, which includes instances of DoSomeMoreWork. So, the object passes this instanceof test. Then, a method runMyFunc() is executed on the object — which means that the method in the latest child that defines the method gets invoked.

If the object is of DoSomeWork, then this executes DoSomeWork::runMyFunc(). If it’s a DoSomeMoreWork object with that method defined, then that method is called. The method could be defined in the BaseClass, as far as OOP is concerned. It’s actually what inheritance is about; not knowing what is the exact class, and still be able to execute a method. There is no unpredictable behavior here.

Apart from that, yes, a lot of inheritance approaches should be solved with composition.

passerby (@birdpoop) wrote at 7/17/2009 2:56 pm:

Good article but some people who still struggle with OOP get really annoyed with the Car->Honda->Civic and Plant->Tree->Oak examples.

You should establish your point with the fruits then back it up with an example a php programmer is likely to actually encounter in reality. Users and User groups or CMS articles or something.

Agree with first comment about misleading title.

Giorgio Sironi (@giorgiosironi) wrote at 7/18/2009 6:00 am:

These are actually common patterns in the object-oriented world, maybe less known in php one. :)
http://www.google.it/search?q=favor+composition+over+inheritance
http://en.wikipedia.org/wiki/Liskov_substitution_principle

Jiri Fornous wrote at 7/18/2009 10:30 am:

To Mathias:

It would be worth for many programmers to explain it in separate article – two points are well known (inheritance vs composition and interface vs implementation), the third is very important one…

derby wrote at 7/19/2009 9:50 pm:

Child classes overriding its parent method should not change the return type. I would imagine that if it is desired to have a similar method but with a different return type, then a new method should be created – since it would seem appropriate that any new functionality calling the method and expecting the different return type would be able to use the newly designated method.

public function getPropertiesArray() { //or something..
return (array) $this->getProperties();
}

Joshua May (@notjosh) wrote at 7/24/2009 2:03 pm:

I think inheritance (especially the argument/example you’ve put up) is more about discipline than it is about making things final and private.

Just because you as the original developer don’t see a reason why anything would need to be subclassed doesn’t mean that someone else won’t.

I’ve run into a lot of headaches before where things were needlessly private (which you can work around, but it’s not good practice) or things were marked final (which is even uglier to work around) because the original developer couldn’t read my mind :)

For similar reasons people say PHP is good (beaucse it’s so flexible, even if that flexibility comes at the cost of readability) OO is also good. It’s a loaded gun aimed right at your foot..don’t pull the trigger ;)

Avi Block wrote at 7/24/2009 6:25 pm:

@Joshua Can you post an example of that which could not be solved by composition?

Joshua May (@notjosh) wrote at 7/25/2009 4:00 am:

@Avi I wasn’t saying there isn’t more than one way to skin a cat, but it’s often less elegant or harder to maintain than if it was just simply done right in the first place, and allowing people to make their own decisions.

Samuel Folkes (@SamuelFolkes) wrote at 8/10/2009 2:17 pm:

This article was well written and the examples were crystal clear. I really don’t see what the fuss is about with some of the comments above. Levels of inheritance simply MUST follow a logical hierarchy or else you are setting yourself up for problems. The only (minor) gripe I have with this article is that the problem(s) outlined here are not exactly direct results of using multiple levels of inheritance but more results of bad design and failure to understand correctly the idea behind what an object is. It is imperative that developers understand ‘is…a’ and ‘a…is…a’ relationships before attempting to utilize inheritance in their code. That aside, this was an good article Brandon.

Timo Reitz (@godsboss) wrote at 11/2/2009 11:29 am:

I think the article describes two completely different problems.

The first one being deep inheritance trees. Those deep trees tie all the classes within a hierarchy together, so if you change one, you often have to look at the others. So avoid these. Sometimes it can be better to use composition instead of inheritance, even if inheritance would not be a logical flaw (like the fruit-salad example). So Apple would not inherit from Fruit, but instead wraps a Fruit and delegates some method calls to it. I think this really is what “Favor composition over inheritance” is about.

The second problem is what you mentioned – don’t confuse “is-a” with “has-a”. This is not what I think “Favor composition over inheritance” is about, because using inheritance if you have “has-a” relationship is not a matter of choice, but simply wrong!

« »

Copyright © 2023 by Brandon Savage. All rights reserved.