In my previous post, I introduced the SOLID design principles and looked at the Single Responsibility principle (SRP) in more detail. This week I’ll investigate the Open-Closed principle (OCP).
The Open-Closed Principle (OCP)
“Reusable software entities (classes, modules, etc.) should be open for extension, but closed for modification”.
A class should be extensible without requiring modification of the original class. In other words, in an ideal world we should never need to change existing code or classes. All new functionality can be added by creating new subclasses and overriding methods, or by reusing existing code. This prevents us from introducing new bugs in existing code. This means that existing clients of our original code won’t be affected by any new changes.
If we never change it, we can’t break it.
Example
Let’s say we have already developed an EventLogger
class that works well, and is used in a number of existing systems. It logs events to a variety of persistent devices (files, sockets, etc.).
A client requests a change to the EventLogger
class, so that when an event gets logged, an email or SMS notification also gets sent. The temptation will be to just change the existing log()
methods to also send out notifications.
However, this means that we will then be creating an additional dependency on a NotificationGenerator
class in our original EventLogger
class. All the existing systems will have to be updated with the new classes. Our clients will have to configure email and SMS servers to support the new classes.
But what about the clients who do not require notifications, or do not have email/SMS connectivity? Their systems will break, which leads to unhappy clients and lost revenue.
Solution
Rather than modify the existing EventLogger
class, what we should do is extend it using inheritance. We would create a NotifyingEventLogger
class that calls the existing log()
methods, and adds notifications.
This means that none of the existing systems will break, and that only those clients who need the new functionality will use the NotifyingEventLogger
class.
// existing class public class EventLogger { public void log() { // appropriate logging code } } public class NotifyingEventLogger extends EventLogger { // new dependency private NotificationGenerator notificationGenerator; // constructor public NotifyingEventLogger(NotificationGenerator notificationGenerator) { super(); this.notificationGenerator = notificationGenerator; } @Override public void log() { super.log(); notificationGenerator.notify(); } }
This shows that we need to:
- Think carefully about the abstractions when doing the design. The key to OCP is to use polymorphism correctly.
- Write classes so that they can be extended without modifying the base class.
- Add new code when extending the behaviour of a class, and not modifying existing code.
What’s next?
In next week’s tip, we’ll look at the following principle: the Liskov Substitution principle (LSP).
As always, please share your thoughts and comments.