Levels of inheritance

From a technical perspective, are there limits to how many levels deep inheritance can go?

Same question from a practical perspective. Is there a rule of thumb that says if you’ve gone x levels deep, you’ve probably done something wrong on the design side?

In the real world, can and does this cause problems and if so, what types of problems should I expect or look for? i.e. performance, readability, usability, etc.

Thanks

Technically I don’t imagine there is a limit that is virtually impractical. Practically speaking, it is usually accepted as a best practice that inheritence be avoided in favor of composition - although inheritence still makes sense.

As a basic rule of thumb, the deeper the inheritence heirarchy the more rigid your code will become and thus more difficult to change so inherit with caution and favor inheritence until it becomes glaringly clear that inheritence is a better/more elgant solution than composition.

Cheers,
Alex

To answer your last question, deep inheritence trees can cause a few issues, mainly code comprehension. The indirection caused by deeply nested hierarchies means you have to step through code in sequence as opposed to random access when investigating a method. Some IDEs and documentation tools will assist you but it’s usually more work than a flat collection of classes.

Using DI/composition the relationship (or rather usage/linkage) of classes is usually implied at the time of calling code, not disguised in deep hierarchies. That is to say, just looking at the container class at the time of it’s use I can usually deduce exactly what classes are involved right there, not having to read docs or traverse complex hierarchies to figure which classes are being pulled into scope.

Also, ridigidy of code. There are many examples of rigid hierarchies, but assume a DB class (which is most common) where someone inherits a custom DB class from say a MySQL base class. Sometime later in the future, MySQL is dropped in favor of MSSQL and the base class must now be adjusted/refactored to reflect the RDBMS change.

Properly designed DB classes/library would allow a more fluid/dynamic change without possibly even requiring a change to the code - which makes the code more dynamic

How many times does a change of RDMS actually occur? There is such a thing as over preparing and causing the software to become nightmarishly complex trying to account for situations that will never arise.

How many times does a change of RDMS actually occur? There is such a thing as over preparing and causing the software to become nightmarishly complex trying to account for situations that will never arise.

Common counter argument. I suppose it depends on your market segment. I worked for a well known white label software development firm with a global customer base in the past. There were enoguhb requests for PG or MSSQL (or other) support for me to justify looking into properly abstracting the RDBMS in order to accommodate even more customers.

I think you missed my point though, it was not whether DB abstraction was practical or not, it was as common real-world scenario of less than ideal usage of inheritence.

class CMySQL{
  // Implement methods
}

class Database extends CMySQL{

}

$db = new Database();
$db->connect($h, $u, $p);

$db->query('SELECT * FROM table LIMIT 1, 25');

This is IMO more work to maintain and comprehend than a dynamic and upfront DI solution, which might look like:

class MySQL{

}

class Database{

}

$db = new Database(new MySQL($h, $u, $p));

No changing inheritence and breaking interfaces potentially (refactoring made easier). Plus the DB in question is clearly obvious - no browsing docs or relying on advanced IDE features to determine which DB is being used. Additionally (and perhaps in the case of a RDBMS is a moot point as you mention) the fact is, you can easily swap out provider/implementations, even dynamically using a strategy/factory.

Cheers,
Alex

Hello.

Agreed; I’ve only encountered a RDBMS change once in my years of development, and even that went rather smoothly. It generally is a bad example because it’s not only the inheritance that might cause troubles, it’s the dialect of SQL just as well.

Nevertheless, I’ve found that composition generally causes less headaches than inheritance, but it’s no silver bullet either. Actually, the two techniques tend to work together to get the most flexible design possible. You’ve probably heard of the rule of thumb that you should only ever use inheritance if there’s an “is-a” relationship.

I’ve extended that rule for myself: the type has to be the same (so Guest is a User), and the motivation for inheritance should be changing the existing behaviour rather than reusing code. If there’s a few lines of code I want to reuse, I try to split it out into another object and inject that object into the client.

Another rule of thumb is that I try never to extend more than once, so I’m not setting up inheritance trees. As PCSpectra already mentioned, experience tells me that a large inheritance tree can be a disaster to debug. Apart from the debugging, there are other problems introduced by inheritance; for starters it’s harder to test as you can’t simply inject a mock or stub to replace the parent object.

The inability to test is a direct result of the tight coupling between parent and child. Child-classes can make a whole lot of assumptions about the state the parent is in and indeed, most child classes I write do so. That’s not a bad thing per se, but if it’s like to change it would be, as it’s harder to change the behaviour of a child class than it is once you’ve gone the mixing and matching route.

This is not carved in stone, obviously. It takes experience and a healthy dose of logic to find the best ratio. I think that it is a good point of view to favour composition over inheritance, but I wouldn’t say inheritance is useless, of even bad.

Yes, of course. If the chance of change is low, you shouldn’t be worrying a whole lot about it. Nevertheless, the cost of abstracting repetitive code into a different object and inject that object into the object that uses that behaviour is not that much higher than extending the object to reduce repetitive code. It doesn’t get “nightmarishly complex” as you put it, if you try to keep sane and remember that at the end of the day, accomplishing the goal is more important than the means to accomplish it.

You can shoot yourself in the foot with either way; the only thing I can tell you is that the hole in my foot was bigger when I had a large inheritance tree.

You can shoot yourself in the foot with either way; the only thing I can tell you is that the hole in my foot was bigger when I had a large inheritance tree

Amen :slight_smile:

Pretty funny, I get the picture