Software entities should be open for extension, but closed for modification.
Consider an example where we implement a Shape class that can calculate the area of that shape:
# shapes_ocp.py
from math import pi
class Shape:
def __init__(self, shape_type, **kwargs):
self.shape_type = shape_type
if self.shape_type == "rectangle":
self.width = kwargs["width"]
self.height = kwargs["height"]
elif self.shape_type == "circle":
self.radius = kwargs["radius"]
def calculate_area(self):
if self.shape_type == "rectangle":
return self.width * self.height
elif self.shape_type == "circle":
return pi * self.radius**2Note
To extend the class we have to modify it, thus invalidating the OCP.
This satisfies the Single Responsibility Principle (SRP), since it is entirely concerned with the management of shape data, and nothing else. However, to add new shapes (say we want a square, or a triangle) we would be required to modify the constructor with further if/else statements.
Additionally, the class requires different arguments depending on the shape selected - if “circle” is passed, we must also pass “radius” - none of which is self-evident from the method signatures. The solution is as follows:
This solution uses OOP interfaces, OOP Polymorphism, and Single Responsibility Principle (SRP). Doing so means that extend the Shapes is open and easy, but requires no modification of the Shapes base class.
Note that this also ties in with Dependency Injection, since it allows us to inject a class without modifying the class being injected - thus allowing us to have a Clean Architecture.