OCP: Modifying OldClass instead of extending
“Software entities … should be open for extension, but closed for modification.”
This statement, at first, seems like a contradiction since it asks you to program entities (class/function/module) to be both open and closed. The open-closed principle (OCP) calls for entities that can be widely adapted but also remain unchanged. This leads us to create duplicate entities with specialized behavior through polymorphism.
Through polymorphism, we can extend our parent entity to suit the needs of the child entity while leaving the parent intact.
Our parent entity will serve as an abstract base class that can be reused with added specializations through inheritance. However, the original entity is locked to allow the program to be both open and closed.
The advantage of OCP is that it minimizes program risk when you add new uses for an entity. Instead of reworking the base class to fit a work-in-progress feature, you create a derived class separate from the classes currently present throughout the program.
We can then work on this unique derived class, confident that any changes we make to it will not affect the parent or any other derived class.
OCP implementations often rely on polymorphism and abstraction to code behavior at a class level rather than hard-coding for certain situations. Let's see how we can correct an area calculator program to follow OSP:
// Does not follow OSP
public double Area(object[] shapes)
{
double area = 0;
foreach (var shape in shapes)
{
if (shape is Rectangle)
{
Rectangle rectangle = (Rectangle) shape;
area += rectangle.Width*rectangle.Height;
}
else
{
Circle circle = (Circle)shape;
area += circle.Radius * circle.Radius * Math.PI;
}
}
return area;
}
public class AreaCalculator
{
public double Area(Rectangle[] shapes)
{
double area = 0;
foreach (var shape in shapes)
{
area += shape.Width*shape.Height;
}
return area;
}
}
This program does not follow OSP because Area()
is not open to extension and can only ever handle Rectangle
and Circle
shapes. If we want to add support for Triangle
, we'd have to modify the method, so it is not closed to modification.
We can achieve OSP by adding an abstract class Shape
that all types of shapes inherit.
public abstract class Shape
{
public abstract double Area();
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double Area()
{
return Width*Height;
}
}
public class Circle : Shape
{
public double Radius { get; set; }
public override double Area()
{
return Radius*Radius*Math.PI;
}
}
public double Area(Shape[] shapes)
{
double area = 0;
foreach (var shape in shapes)
{
area += shape.Area();
}
return area;
}
Now each subtype of shape handles its own area calculation through polymorphism. This opens the Shape
class to extension because a new shape can easily be added with its own area calculation without error.
Further, nothing in the program modifies the original shape, and it will not need to be modified in the future. As a result, the program now achieves the OCP principle.