What Does OCP Try To Solve?
When we talk about OCP, we're focusing on the idea that classes, modules, and functions should be open to extension but closed to modification. In simpler terms, this principle aims to prevent modifying existing, tested code when new features or changes are needed.
Instead, the focus is on adding new code to accommodate new behaviour, which helps reduce bugs, maintain consistency, and make systems easier to evolve.
The Definition
So, what exactly is the Open-Closed Principle?
“Objects or entities should be open for extension but closed for modification.”
Let’s break this down.
Open for Extension: We should be able to add new functionality or behaviour to an existing system or class.
Closed for Modification: Adding new functionality shouldn’t require altering the original class’s code.
This principle often uses inheritance, composition and polymorphism in object-oriented programming to achieve extensibility, creating a structure that encourages adding new subclasses or implementations instead of modifying the core class itself.
Real-World Example
Consider a notification system that supports sending email notifications to users. Initially, the NotificationService class might look like this:
class NotificationService
{
public function send(string $message): void
{
// Logic for sending an Email
}
}Nothing much wrong with this i’d say but… if you think about it, this setup works fine for email. But what if a new requirement arises to send SMS notifications as well?
Without OCP, we might be tempted to add the SMS logic directly into the NotificationService class, resulting in something like this:
class NotificationService
{
public function send($message, string $type): void
{
if ($type === 'email') {
// Logic for sending an Email
} else if ($type === 'sms') {
// Logic for sending an SMS
}
}
}Adding SMS logic here means we’re modifying a class that’s already working fine for emails. At this point it’s almost becoming a code smell, whether or not we adhere to OCP here (in my opinion) depends on the scope of the project.
If it’s limited in scope and not going to be touched again, then leave it.
If it’s going to grow and incorporate more notification services, it might be worth changing it. This setup will quickly become unmanageable as more notification types are added (e.g., push notifications, Slack messages).
But how do we change it so it’s extendable and future proofed?
First, define a common interface, this’ll ensure that any classes that implement it, have to adhere to it.
interface Notifier
{
public function send(string $message): void;
}
Next, lets create classes for each notification type, which implement the above interface.
class EmailNotifier implements Notifier
{
public function send(string $message): void
{
// Logic for sending an Email
}
}
class SmsNotifier implements Notifier
{
public function send(string $message): void
{
// Logic for sending an SMS
}
}By doing this, we change the NotificationService so it’s CLOSED for modification and OPEN for extension.
class NotificationService
{
public function __construct(protected Notifier $notifier) {
}
public function send($message): void
{
$this->notifier->send($message);
}
}Thats it!
We don’t need to touch the NotificationService anymore, if you need to change the notification sent we can do it when we create the object.
$email = new NotificationService(new EmailNotifier());
$email->send('Some message');
$sms = new NotificationService(new SmsNotifier());
$sms->send('Some message');If we need to tweak a notification, we edit the specific notifier class, if we need to add a new one, we simply create a new class, if we need to delete one… you get the picture.
Final Thoughts
The Open-Closed Principle is all about building systems that adapt gracefully to change. By encouraging developers to extend rather than modify, it keeps code stable, maintains system integrity, and makes future changes simpler.
Think of OCP like building with LEGO bricks. Each piece is designed to snap onto the others without having to reshape or modify the core structure. If you need a new piece, you just add it—no need to break down what’s already there.
All that said, sometimes it’s overkill—it depends on the scope of the problem you’re trying to solve, if you’ve got a tiny piece of functionality that will rarely change, then I wouldn’t bother, but if you’ve got a piece of code that will definitely evolve over the years, yep, OCP….
The Grumpy Dev is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.

