What Is This Principle?

The Open/Closed Principle, as originally formulated by Bertrand Meyer, states that a given software entity should be open for extension, but closed for modification. Essentially, any given class (or module, or function, etc) should allow for its functionality to be extended, but not allow for modification to its own source code.

Open/Closed Principle - Open chest surgery is not needed when putting on a coat

Benefits

This principle aims to reduce the introduction of bugs and other errors into your code by requiring classes to not change their own implementation unless absolutely necessary, as other derived or implemented classes may be relying on that implementation to function properly.

It also wants us to implement classes that can easily have their functionality extended, and by allowing the class to be open for extension, we allow for many real-world changes to occur without completely disrupting our design.

A Simple Example

Let's imagine a scenario in which we are given several Rectangles and need to calculate the total combined area of all of them. We then come along and create a solution that looks something like this:

public class Rectangle
{
    public double Width { get; set; }
    public double Height { get; set; }
}

public class CombinedAreaCalculator
{
    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;
            }
        }
        return area;
    }
}

This code does exactly what we want it to do, and it works great for rectangles. But, what happens if some of our shapes are circles?

public class Circle
{
    public double Radius { get; set; }
}

We have to change the CombinedAreaCalculator to accommodate this:


public class CombinedAreaCalculator
{
    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;
            }
            if (shape is Circle)
            {
                Circle circle = (Circle)shape;
                area += (circle.Radius * circle.Radius) * Math.PI;
            }
        }

        return area;
    }
}

By doing this we have violated the Open/Closed Principle; in order to extend the functionality of the CombinedAreaCalculator class, we had to modify the class's source. What happens when some of our shapes are triangles, or octogons, or trapezoids? In each case, we have to add a new if clause to the CombinedAreaCalculator.

In essence, CombinedAreaCalculator is not closed for modification, and isn't really open for extension (what good would inheriting from CombinedAreaCalculator do for another class?).

So, we need to refactor. There are many ways to refactor this to uphold Open/Closed; I'm going to show you just one of them. Let's create an abstract class that all the shapes can inherit from:

public abstract class Shape
{
    public abstract double Area();
}

Notice that our abstract Shape class has a method for Area. We're moving the dependency for calculating the area from one centralized class (CombinedAreaCalculator) to the individual shapes. The CombinedAreaCalculator will just call each individual Shape class's Area method. The individual shape classes can now be implemented like this:

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 class Triangle : Shape
{
    public double Height { get; set; }
    public double Width { get; set; }
    public override double Area()
    {
        return Height * Width * 0.5;
    }
}

And finally, we can create the new CombinedAreaCalculator class:

public class CombinedAreaCalculator
{
    public double Area(Shape[] shapes)
    {
        double area = 0;
        foreach (var shape in shapes)
        {
            area += shape.Area();
        }
        return area;
    }
}

We've made the Shape abstract class and the CombinedAreaCalculator class open for extension and closed for modification, thereby upholding the Open/Closed Principle. If we need to add any other shapes, we just create a class for them that inherits from Shape and we're good to go.

Potential Hazards

You shouldn't interpret this rule as "don't change already implemented classes, ever." Of course scenarios will arise that will force or require you to change classes that are already implemented. However, we should use discretion when attempting to make these modifications, and keeping OCP in mind allows us to do that in a more efficient manner.

We only need to keep in mind that modules, ideally, should be open for extension and closed for modification. But if we have to change the code to support new rules and requirements, and the best way to support those requirements is to change existing class functionality, we shouldn't be afraid to just do it.

Is It Worth It?

Absolutely, though I don't feel as strongly about it as I do the Single-Responsibility Principle. Still, it's an excellent guide to making any extensions to functionality simpler to implement, and minimizing the work that we programmers have to do to make changes whenever they arise.