Before reading next, if you don’t know what is Composition or what is Inheritance, I highly recommend you to read or watch something about these approaches, because the main goal of this article, is to show you what and when will be better to choose. But, just to be sure, that we are on the same page, let’s discuss a little bit what is what.
Inheritance is one of the core concepts of object-oriented programming, that helps us, developers to avoid code duplication. The main idea is that we create a base class, which contains logic, that will be reused by our subclasses. Let’s see an example:
From the example, we can see that we have a base class “Element” and all our future elements will extend common logic from the Element class.
Also, maybe you heard is-a relationship. What does it mean? For example, “Form” is an “Element” and “Button” is an “Element”.
Another example, we can create a base class “Animal” and our subclasses like “Bird”, “Dog”, “Cat” also has is-a relationship. Why? Because “Bird” is an “Animal”, “Dog” is an “Animal” and “Cat” is also an “Animal”.
And now, let’s speak a little bit about composition. Unlike inheritance, the composition uses a has-a relationship. We collect different pieces of functionality together. Let’s see an example:
In this example, we can see that we have main class “Car” that uses “Engine” and “Transmission”.
In this example, we can’t say that “Engine“ is a “Car” or “Transmission” is a “Car”. But, we can say that “Car” has “Transmission” or “Car” has “Engine”.
I hope that you understand what is Inheritance and what is Composition if you forget.
So, now, let’s look at different examples. First, we look at an example using the class way approach, and then, we will take a look at the function way approach.
Imagine, we are working with the file system and we want to create read, write and remove functionality. What can we do? Just, for example, we can create a class, that will do this, and it’s ok:
I think for this moment everything is ok with this approach.
But, a little bit later, we understand, that we want to have permissions, and some of our users will have just read permission, another one will have write permission. What can we do? One solution is can divide our methods, into different classes, like:
And now, we can use the needed permission for each client. But, now, we have a problem. What, if we need to give someone permission for the reading and for the writing? Or for the reading and for the removing? With the current implementation, we can’t do it. How we can solve it?
The first solution may be could be: to create a class for reading and writing, or for reading and for removing:
If we do this, we will have next classes: FileReader, FileWriter, FileRemove, FileReaderAndWriter, FileReaderAndRemover.
And it’s bad. Why? The first reason is if we have not just 3 methods, but 10, 20. We can have a really big amount of combinations between them. The second reason is that we duplicate logic in our classes. Just, for example, our FileReader class contains read methods, and FileReaderAndWriter also contains duplicate code of the same method.
So, as you can see, it’s a not good solution.
So, what is the solution? One of the possible solutions here is to use composition. How it will look like?
First of all, let’s move our methods to a separate function factory. Why function? Because maybe we want to pass some parameters later. In our example, we don’t need it.
In the above example, we have 2 functions that create objects with the reading or with the writing method.
What does it give us? Now, we can use them in any place where we want and combine them:
In the example above, we created a read and write service. This is almost what we have at the start of our FileReader class (just without the “remove” method).
And now, if we want to combine different permission: for reading and writing, or writing and deleting, we can do it very easily:
If we have 5, 10, 20 methods, we can combine them how we want. We don’t have a problem with duplication code, we don’t have confusing architecture.
Now, let’s see another example, without classes. Imagine, that we have employees. For example: taxi driver, sport coach, and manager:
And everything is ok now. But, imagine a situation, where some employee works as a sports coach, but sometimes or at the night works also as a taxi driver. What can we do?
Create a function like:
Yes, it will work, but the problems are the same as in the first example with classes. We could have really many different combinations with code duplication. So, knowing now, how composition works, we can refactor our code like here:
And now, we can combine all our work types as we want, without duplications and with an understandable approach:
I really hope that now, you can see the difference between inheritance and composition.
In general, inheritance could answer the “is-a” relationship, when composition “has-a”.
But in practice, as we can see inheritance, is sometimes not a good approach. Just in our example, the driver is an employee and the manager also is an employee. But when our task is to combine different pieces into a single item, like here, composition really could be much better than inheritance.
At the end of this article, I want to focus on that both inheritance and composition are good approaches when you are using them in the right place when they should be. In one situation composition is better than inheritance and in another situation vice versa.
And of course, we can combine inheritance and composition together, if we have “is-a” relationship, but we want to add different values or methods, we can have some base class, that gives all common functionality for our instances, and we can also use composition to add other, specific functionality.