The Object Orientation paradigm is one of the most popular within software development. Among the applied concepts there are some that are considered the pillars of the paradigm, namely : Abstraction , Encapsulation , Inheritance and Polymorphism . However, in addition to the pillars, another important concept is that of code reuse, where a certain piece of code can be reused by several classes in order to avoid duplicated coding within the software. And when it comes to reuse, Inheritance may be the first technique that comes to mind, but it is not always the most suitable and instead of solving a problem, it can end up generating countless others as the software grows.
What is Inheritance?
Inheritance is one of the pillars of object-oriented programming and is a commonly used technique when you want to reuse existing code and also describe a relationship between classes so that certain classes can inherit attributes and methods from a higher-level class.
These classes can be described as: Parent Class, Superclass or Base Class, which is the class that contains the characteristics that will be inherited by the lower level classes in the hierarchy.
And there are also those described: Daughter Classes, Derivatives or Subclasses, which are precisely the classes that inherit the characteristics of their base class and can also contain their own characteristics, or even refine (specialize for your context) a certain characteristic inherited from your base class.
In the proposed hierarchy above you can check the superclass Calendar and analyze what classes WeeklyCalendar and MonthlyCalendar have a relationship is the type regarding the superclass Calendar , after all these classes represent a kind calendar, but more specialized.
Thus, the use of Inheritance can be applied as a technique to describe the relationship between classes and also apply code reuse. It is also important to note that despite using Inheritance to reuse certain code snippets, each type of calendar can also specialize the characteristics that are necessary for its context, in the case of the example each calendar implements its own method date_range.
What are the main problems with using Inheritance?
If Inheritance does all of the above, for what reasons can it be bad for development?
One of the main problems encountered when making massive use of Inheritance is the high coupling and low encapsulation of information from each class.
The greater the coupling, the more difficult it is to change classes, make modifications to a base class without this modification propagating to its child classes, or in more serious cases, an error written in one class will propagate to the others within the hierarchy. And the low encapsulation is due to the fact that the classes involved can end up having a lot of knowledge of the internal state of other classes.
Another problem related to the self-coupling created by Inheritance is that it makes it difficult for behaviors to be added or removed at runtime, or to make objects have other behaviors for different contexts at different times within the software.
A subclass does not always need to inherit all attributes or methods of its superclass, in this case there is a breach of the Interface Segregation Principle, which dictates that classes should not implement what they will not use just to satisfy the Inheritance.
Finally, Inheritance may seem like a good alternative at first, but as time goes by and the software grows, classes start to grow and gain different behaviors, getting more and more different until reaching a point that the relationship between classes previously said as it’s like X to be more like sharing some functionality with Y , so it doesn’t make sense to use Inheritance anymore.
What is Composition?
Composition, like Inheritance, is a form of code reuse, but it has certain advantages over Inheritance that make it a more secure and flexible option. Composition describes a relationship has a (skill) between two objects. That is, that way any object that needs a certain skill can have that skill when it needs it.
Composition as opposed to Inheritance is loosely coupled and allows objects to work independently, each with its own responsibility.
How does Composition solve the problems that Inheritance can generate?
Based on the examples described above, it is possible to have an idea of how the use of Composition will help to solve the problems encountered with the use of Inheritance.
Inheritance is a static relationship between objects, once the relationship is made it is difficult to undo it. Composition, on the other hand, is a dynamic relationship that allows for greater flexibility, extensibility and can be added to the object when needed. A strong example of this is the Decorator design pattern that makes use of Composition to add new behaviors to objects at runtime. Other design patterns like Strategy also favor Composition over Inheritance.
By allowing the addition and removal of behaviors at runtime, Composition also allows an object to have various roles and responsibilities during its lifetime within the software, changing those behaviors at times.
When used correctly, it also encourages software to adhere to SOLID development principles by creating classes that have unique responsibilities, are open to extension, and favor dependency injection.
Finally, because it has no knowledge of an object hierarchy and focuses on classes that do only a certain job, Composition provides greater encapsulation for objects.
But now, which one do I use?
- It works well for cases where the modeling is well defined and also for cases where it is necessary to centralize what is common in the hierarchy and specialize what is different, as in the Calendar example.
- When within the hierarchy the lower-level classes represent a relationship it is of type with respect to the base class.
- When a relationship is less hierarchical and tends to change as the software grows, it is a better choice, as it will allow several objects to have a certain common ability, without requiring a major change in its structure.
- It’s more flexible and allows objects to be extended at runtime.
- Because of these characteristics, it fits well in situations where there are several changes in business rules and classes that can gain or lose certain characteristics as the software evolves.
- Model how well the hierarchy and the exchange of messages between your classes will work to get a better sense of what will happen and know how to make a better decision between Composition and Inheritance.
- Use Inheritance carefully, in general favor the use of Composition over Inheritance.
- Choosing Composition will make your classes more reusable, flexible and easier to maintain.
- Composition lets you add/remove behaviors at runtime and create less-coupled code and favors encapsulation.
- Golang, for example, a relatively new language does not implement the use of Inheritance, only Type Composition, in order to avoid the problems pointed out by the use of Inheritance.
And do you have any experience regarding the points raised in the post that you would like to share? Leave your comment.