Open/Closed Principle (OCP)
“Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”
This means you can add new functionality without changing existing code.
Purpose
- Adapts to new requirements with minimal risk: new features can be added by extending existing code (e.g. via inheritance
or composition) rather than altering working code.
- Prevents regressions, since existing classes remain untouched when extending behavior.
- Encourages flexible designs using polymorphism, interfaces, or abstract classes, so that core logic remains stable over time.
Minimal Example
The first function below violates OCP
: adding a new shape (e.g., Triangle
) would require modifying the function to handle
it. The improved design uses polymorphism – an abstract Shape
base class with a defined area() method. New shapes can be
extended by creating a subclass and overriding area(), with no changes needed to existing code.
# Without OCP: adding new shapes requires modifying this function (not extensible)
def get_area(shape):
if isinstance(shape, Circle):
return 3.14 * shape.radius ** 2
elif isinstance(shape, Rectangle):
return shape.width * shape.height
# If a new shape type is added, we would have to modify this function.
# With OCP: define a common interface for shapes
class Shape:
def area(self):
raise NotImplementedError
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * (self.radius ** 2)
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
# Now, adding a new shape (e.g., Triangle) means creating a new subclass of Shape
# without changing any existing code or functions that use Shape.area().
More Realistic Example
Imagine a notification system that sends alerts via multiple channels (email, SMS, etc.). Using OCP
, we define a base
NotificationSender
class and extend it for each channel. The client code can treat all senders uniformly through the base
interface. Adding a new channel (say, a push notification) is as simple as creating a new subclass, with no modifications
to the existing sending logic.
class NotificationSender:
def send(self, message):
raise NotImplementedError
class EmailSender(NotificationSender):
def __init__(self, email_address):
self.email = email_address
def send(self, message):
print(f"Email to {self.email}: {message}")
class SMSSender(NotificationSender):
def __init__(self, phone_number):
self.phone = phone_number
def send(self, message):
print(f"SMS to {self.phone}: {message}")
# Client code works with the abstract NotificationSender type:
notifiers = [EmailSender("team@example.com"), SMSSender("555-1234")]
for sender in notifiers:
sender.send("Server is down!")
# Output:
# Email to team@example.com: Server is down!
# SMS to 555-1234: Server is down!
# If we need to add a PushNotifier later, we can create a PushNotifier class
# extending NotificationSender without changing the loop or other existing code.
Responses are generated using AI and may contain mistakes.