SOLID Principles in Depth: The Open/Closed Principle (OCP)
By Misbahul Munir3 min read645 words

SOLID Principles in Depth: The Open/Closed Principle (OCP)

Backend Development
solid principle
clean code

In our journey through the SOLID principles of object-oriented design, we’ve explored the Single Responsibility Principle (SRP)—a foundation for clean and maintainable code. Now, let’s dive into the Open/Closed Principle (OCP), a rule that helps your system grow without breaking existing code.


What is the Open/Closed Principle?

Definition (Bertrand Meyer, 1988):

"Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification."

In simple terms:

You should be able to extend a class’s behavior without changing its existing code.

This principle encourages developers to write code that is resilient to change. Instead of modifying existing, working code every time a requirement changes, you extend it via inheritance, composition, or polymorphism.


Why OCP Matters

  • Minimizes risk – Changing existing code can break functionality.
  • Improves maintainability – Extensions can be added without touching tested components.
  • Encourages modular design – Clean separation of core logic and variant behavior.

Real-World Analogy: Power Plug Adapters

Think of your laptop charger. When traveling to different countries, you don’t throw away the charger and build a new one—you just add an adapter.

The charger (core logic) stays the same. The adapter (extension) makes it compatible with a different system.

That’s OCP in action: extend behavior without modifying the original.


Code Example: OCP Violation

Suppose you’re building a notification system:

class NotificationService: def send(self, message, type): if type == "email": self.send_email(message) elif type == "sms": self.send_sms(message) def send_email(self, message): print(f"Sending Email: {message}") def send_sms(self, message): print(f"Sending SMS: {message}")

Now your PM says: “We need to support push notifications.”

You'd need to modify the class, which risks breaking existing logic—a clear violation of OCP.


Applying OCP: Refactored Design

Let’s refactor it using polymorphism:

class Notifier: def send(self, message): raise NotImplementedError class EmailNotifier(Notifier): def send(self, message): print(f"Sending Email: {message}") class SMSNotifier(Notifier): def send(self, message): print(f"Sending SMS: {message}") class PushNotifier(Notifier): def send(self, message): print(f"Sending Push Notification: {message}") class NotificationService: def __init__(self, notifiers): self.notifiers = notifiers def send_all(self, message): for notifier in self.notifiers: notifier.send(message)

Now, to add push notifications, you don’t touch

NotificationService
at all—you just add a new class that implements the same interface.


Real-World Example: E-commerce Discount Strategy

In an e-commerce app:

class DiscountService { public double calculateDiscount(Order order, String customerType) { if (customerType.equals("REGULAR")) { return order.getTotal() * 0.05; } else if (customerType.equals("PREMIUM")) { return order.getTotal() * 0.10; } return 0; } }

This is fine—until marketing wants to add more customer types, seasonal discounts, coupons, etc.

Every new rule means modifying this class, violating OCP.

OCP-Compliant Design:

interface DiscountStrategy { double getDiscount(Order order); } class RegularCustomerDiscount implements DiscountStrategy { public double getDiscount(Order order) { return order.getTotal() * 0.05; } } class PremiumCustomerDiscount implements DiscountStrategy { public double getDiscount(Order order) { return order.getTotal() * 0.10; } } class DiscountService { public double calculateDiscount(Order order, DiscountStrategy strategy) { return strategy.getDiscount(order); } }

Now you can plug in any new discount strategy without ever modifying

DiscountService
.


Tools to Help Practice OCP

  • Interfaces & Abstract Classes – Define stable contracts for extension.
  • Composition over Inheritance – Prefer injecting behavior over overriding it.
  • Strategy & Factory Patterns – Common design patterns that support OCP.

Pro Tips

  • If your
    if
    or
    switch
    statements are growing with every new feature, it's a red flag.
  • Use interfaces or base classes as extension points.
  • Test extensions independently from the core logic.

Conclusion

The Open/Closed Principle encourages you to think ahead—structure your code not just for what it does now, but how it might evolve tomorrow. By designing for extension instead of modification, you future-proof your applications and build systems that are flexible, scalable, and safe to change.


Up Next: The Liskov Substitution Principle—don’t miss it!