Inheritance! it’s probably the concept that gets us excited when we start learning Object-Oriented Programming (OOP). We gain code reuse in a straightforward way! but is the white box reuse a good thing? let’s explore why we should Favor Composition over Inheritance.
What’s the Purpose of Inheritance and Composition?
Inheritance
The mechanism of basing an object or class upon another object or class, retaining similar implementation….and forming them into a hierarchy of classes.
We have two important points to notice for Inheritance:
- Used to grouping classes with the same semantics – organize concepts from generalized to specialized. The well known IS-A relationship.
- Code Reuse – reuse by sub-classing and often referred to as white-box reuse, because the internal details of a parent class are often visible to sub-classes.
Composition
New functionality is obtained by assembling or composing objects to get more complex functionality.
For Composition is almost the same:
-
Used to Break down complex problems into modular solutions – it allows us to compose complex objects using other objects, the HAS-A relationship.
-
Code Reuse – often referred to as black box reuse, because no internal details of objects are visible.
Why Should We Favor Composition?
Let’s explore that with some code examples. Starting with a common mistake that breaks the fundamental rule of Inheritance the IS-A relationship.
public class Team extends ArrayList<Player> { // attributes; // methods; // ... }
This class is semantically wrong, because the statement ‘A Team IS-A arrayList’ is not true, as Inheritance makes easier to archive code reuse. It’s a common mistake to group unrelated classes, and this naive looking for code reuse can cause a huge problem with Inheritance, because:
When a subclass overrides some but not all operations, it can affect the operations it inherits as well.
For example, we have an Email Manager class that each time an email is removed, it saves the removed email in a different list.
public class EmailManager { private List<Email> emails = new ArrayList<>(); private List<Email> removedEmails = new ArrayList<>(); public void add (final Email email) { emails.add(email); } public void remove(final Email email) { emails.remove(email); this.setRemoved(email); } public void setRemoved(final Email email) { email.setDeleted(true); this.removedEmails.add(email); } }
Now let’s say a client needs to create his own Email Manager and wants to show a notification after removing the email, so he creates the following class:
public class ClientEmailManager extends EmailManager { @Override public void setRemoved(final Email email) { PopupNotificationService.showRemovedNotification(email); } }
He overrides the setRemoved method and with that brakes the implementation of the remove method in the EmailManager class. It doesn’t seems to be a big problem in this examples. Now think about that in a class that is the eight in the hierarchic, furthermore Inheritance:
Exposes a subclass to the details of it’s parent class, it’s often said that inheritance breaks encapsulation… any change in the parent’s implementation will force the subclass to change.
Inheritance is defined statically at compile-time and we can’t change the implementation inherited from the parent class at run-time.
Imagine a new requirement, now we want to show a notification based in the user configuration. How can we do that in the class above if the PopupNotificationService cannot be dynamically changed? well – we can’t.
The Power of Composition
Composition is defined dynamically at run-time through objects acquiring references to other objects. Composition requires objects to respect each others interface.
Our class would look like this:
public class ClientEmailManager extends EmailManager { private INotificationService notificationService; public ClientEmailManager(final INotificationService notificationService) { this.notificationService = notificationService; } @Override public void setRemoved(final Email email) { super.setRemoved(email); notificationService.showRemovedNotification(email); } }
As Composition is defined dynamically, any implementation of INotificationService can be replaced at run-time in the ClientEmailManager class, as we follow the principle Programming to a interface not an implementation, we can define a group of INotificationService, where in turn the ClientEmailManager delegates the Notification Operation to a INotificationService instance.
Points to Keep in Mind
- Class Inheritance is defined statically while object Composition is defined dynamically.
- Be careful when overriding some but not all methods of a parent class. This can have undesired consequences.
- Inheritance breaks encapsulation, a change in the parent class can force a change in the sub classes, while Composition respects the interface defined by the objects.
- Last but most important – Object Composition and Inheritance can work together!