Inheritance vs. Object Composition
Composition vs Inheritance
Inheritance is the major paradigm of object oriented programming. It is what most people think of when you think of classes. It’s what we all learn in school and we think it comes pretty much hand in hand with OOP. Using classes and objects is all about code re-use and code organization. But inheritance is not the only way to achieve these things. Furthermore, inheritance can get nasty if you create a very deep inheritance tree and then realize you need a new type of object that spans separate branches of the tree and so doesn’t fit neatly into your inheritance model.
Inheritance is just a tool, and like any tool it isn’t the best tool for every situation. Another OOP tool is called object composition. Composition involves building an object from parts, rather than inheriting from more generic types. Object composition allows you to build up objects free of an inheritance tree and therefore you don’t have to worry about later changing a super class and having it break things in various levels of child classes.
That isn’t to say that inheritance is bad. Inheritance is a very powerful tool, but it can become unwieldy if overused. So when designing your Object Oriented code, you should generally favor a mix of inheritance and composition, but ultimately just choose the tool that works the best and that you know won’t break your code later.
When to use inheritance?
The two rules to follow when using inheritance are:
- Keep it shallow
- Never the twain shall meet
Rule 1, keep it shallow, means that you should not build inheritance many levels deep because if later on you need to change some functionality higher up the inheritance tree it will have widespread consequences on a lot of your other object types that inherit from it. This can result in two things, either breaking a lot of the code or just creating a lot of bloat.
Code can break when you decide you need to change the functionality of some object type but you have a lot of code built around generations of ancestors of that object type that rely on the current functionality. When you change that functionality it might break a lot of code and if it affects many layers of inheritance then fixing everything might be a nightmare. Or you might realize you didn’t do a good job in planning out all your needs when designing your inheritance tree and so you have to go back and completely refactor a large inheritance tree.
Even if it doesn’t break code it can lead to bloat. The old “you asked for a banana and got the banana, the gorilla holding the banana, as well as the whole jungle.” If you realize you need to add some functionality or data properties to some of your object types in your inheritance tree and you need to add those things somewhere in the middle of the tree, that code is going to be added to a lot of object types and therefore a whole lot of object instances. If all those objects need those extra features then it is okay, but maybe you added those features because only a small amount of object types needed them, but those types are spread across different branches of the tree. Well now you’ve added features to a bunch of object types that don’t need those features, this creates bloat. Furthermore, if another developer comes along and sees those things available in those objects that aren’t supposed to have them, he/she may think they are supposed to be there and try to use them. Bloat in an of itself is bad, but this type of inheritance bloat can lead to things being where they shouldn’t which can create developer confusion, therefore wasted time, wasted money, and pretty much the end of existence as we know it. This is how we get Terminators on accident.
Rule 2, never the twain shall meet, means that you are safe to create a new level of inheritance as long as you are 100% sure that siblings in each generation will never need to overlap. Below is an image representing an inheritance tree with 4 generations of object types, labeled Root and then 1 through 3. As you develop your application you must never run into a case in which you need to create an object type that needs to share characteristics with one of its cousins, the whole point of inheritance is that you are creating object types that have the characteristics of their descendent, not their cousins or siblings. If you ever run into this case that means inheritance was the wrong tool to use and you should use composition.
For example, if objects created from the third Gen 3 object type need to share characteristics that were inherited in the fourth Gen 3 object type, welp, sorry but your inheritance tree is broken. Again, the whole point of inheritance is that divergent branches lead to different characteristics, and any characteristics that are shared between branches should be given to the root of those branches’ subtree. So if the first three Gen 3 object types on the left need to have a certain property, it should have been created in the first Gen 1 object type and inherited. Inheritance needs to be clean for it to make sense. If you’re making a half-breed that means you’re doing it wrong.
When to use composition?
Basically, use composition whenever you aren’t 100% sure that you will have no problems using inheritance. The mantra “favor composition over inheritance” speaks to the fact that new situations always arise in software development so when you are starting out on an application you can never be 100% sure that you won’t run into problems with inheritance and need to refactor your inheritance tree at some point. Not saying you should never use inheritance, but you just need to be aware of the risks and make the appropriate decision.
When to use both?
If you want especially powerful Object Oriented code you should use both inheritance and composition where it fits your needs. For example, you might find you need a few base object types that you want to inherit from, so create a shallow inheritance tree and then use composition to fill in the other details of your object types beyond those base needs you handle with inheritance.
For example, say you are making a web application for businesses and the users of the application are split up into their various career fields, let’s say Sales, Management, Engineering, HR, Payroll, Custodial, and Executive. You’d probably want to make a base User object type which all users inherit from that would have universally shared attributes like name, phoneNumber, address, employeeId, wage, yearsAtCompany, as well as common operations like showCompanyPolicies(), showPayStubs(), editPublicDetails(), sendCompanyMessage(), requestTimeOff(). It would make total sense to include all that in a User type which all users inherit. You might then have a second generation of inheritance with two children of the User type called Salaried and Hourly. No employee can possibly be both salaried and hourly so that is a clean separation in our inheritance tree. We will never run into a problem in which we realize we have to later create a half-breed type of Salaried/Hourly object. Inheritance works here and makes sense. Now let’s say it is possible to work in a couple different areas of the company. Maybe an Executive also does some work as an engineer in the company, or someone splits time between payroll and HR, or obviously a sales manager is both part of sales and management. Here is a case where we need user objects to be a part of divergent object types, so we want to use composition instead of inheritance because this would break our ‘never the twain shall meet’ rule of inheritance. I mean you could make Sales, Manager, SalesManager, Payroll, HR, PayrollHR, etc object types but that is terrible design because you would have to update your code anytime an employee in one of your clients made a new shared role that you didn’t think of and you are not re-using code so you would have to make a lot of different object types to cover the various possible situations. Just plain bad.
What you should do in this example is each user should inherit from User->Salaried or User->Hourly and then use object composition to fill in the rest of the details for each employee. You can create standalone objects for each different employee type and add them to each user object as needed. So when an engineer becomes an executive but keeps working in engineering as well, in your application all you have to do is throw the executive object into that employee’s user object and you’re good to go. Obviously this is something you can build into the application itself so it doesn’t require any refactoring like it would with inheritance.
Inheritance and Object Composition are just tools of OOP. To have good Object Oriented design in your application you should use both tools where appropriate. Don’t make inheritance trees too deep and when in doubt use composition. Combine these two tools together to maximize code re-use and code organization, thereby leading to maximized code simplicity.