Programming  /  Architecture July 13, 2010

A simple example of the Open/Closed Principle

A few weeks ago I did a presentation titled “How uncle Bob changed my life: An introduction to the SOLID principles” for a Swedish user group. I have previously written about a real world example of the Open/Closed Principle but during the presentation I used a much simpler example which I thought illustrated the principle quite well. I thought I’d post it here as well.

What is the Open/Closed Principle?

Let’s begin with a short summary of what the Open/Closed Principle is. It’s a principle for object oriented design first described by Bertrand Meyer that says that “software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification”.

At first thought that might sound quite academic and abstract. What it means though is that we should strive to write code that doesn’t have to be changed every time the requirements change. How we do that can differ a bit depending on the context, such as our programming language. When using Java, C# or some other statically typed language the solution often involves inheritance and polymorphism, which is what this example will illustrate.

An example – calculating area

Let’s say that we’ve got a Rectangle class. As most rectangles that I’ve encountered it has a width and a height.

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

Now our customer, Aldford (which apparently means “old river-ford”, did you know that?), wants us to build an application that can calculate the total area of a collection of rectangles.

That’s not a problem for us. We learned in school that the area of a rectangle is it’s width multiplied with it’s height and we mastered the for-each-loop a long time ago.

public class AreaCalculator
{
    public double Area(Rectangle[] shapes)
    {
        double area = 0;
        foreach (var shape in shapes)
        {
            area += shape.Width*shape.Height;
        }

        return area;
    }
}

We present our solution, the AreaCalculator class to Aldford and he signs us his praise. But he also wonders if we couldn’t extend it so that it could calculate the area of not only rectangles but of circles as well.

That complicates things a bit but after some pondering we come up with a solution where we change our Area method to accept a collection of objects instead of the more specific Rectangle type. Then we check what type each object is of and finally cast it to it’s type and calculate it’s area using the correct algorithm for the type.

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;
}

The solution works and Aldford is happy.

Only, a week later he calls us and asks: “extending the AreaCalculator class to also calculate the area of triangles isn’t very hard, is it?”. Of course in this very basic scenario it isn’t but it does require us to modify the code. That is, AreaCalculator isn’t closed for modification as we need to change it in order to extend it. Or in other words: it isn’t open for extension.

In a real world scenario where the code base is ten, a hundred or a thousand times larger and modifying the class means redeploying it’s assembly/package to five different servers that can be a pretty big problem. Oh, and in the real world Aldford would have changed the requirements five more times since you read the last sentence :-)

A solution that abides by the Open/Closed Principle

One way of solving this puzzle would be to create a base class for both rectangles and circles as well as any other shapes that Aldford can think of which defines an abstract method for calculating it’s area.

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

Inheriting from Shape the Rectangle and Circle classes now looks 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;
    }
}

As we’ve moved the responsibility of actually calculating the area away from AreaCalculator’s Area method it is now much simpler and robust as it can handle any type of Shape that we throw at it.

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

    return area;
}

In other words we’ve closed it for modification by opening it up for extension.

When should we apply the Open/Closed Principle?

If we look back our previous example, where did we go wrong? Clearly even our first implementation of the Area wasn’t open for extension. Should it have been? I’d say that it all depends on context. If we had had very strong suspicions that Aldford would ask us to support other shapes later on we could probably have prepared for that from the get-go. However, often it’s not a good idea to try to anticipate changes in requirements ahead of time, as at least my psychic abilities haven’t surfaced yet and preparing for future changes can easily lead to overly complex designs. Instead, I would suggest that we focus on writing code that is well written enough so that it’s easy to change if the requirements change.

Once the requirements do change though it’s quite likely that they will change in a similar way again later on. That is, if Aldford asks us to support another type of shape it’s quite likely that he soon will ask for support for a third type of shape.

So, in other words, I definitely think we should have put some effort into abiding by the open/closed principle once the requirements started changing. Before that, in most cases, I would suggest limiting your efforts to ensuring that the code is well written enough so that it’s easy to refactor if the requirements starts changing.

More examples of the Open/Closed Principle?

If you’ve got any other good and straight forward examples of the open/closed principle I’d love to hear about them as I really enjoy studying the SOLID principles in general and different ways to apply OCP in particular.

Any other feedback is of course also most welcome!

PS. For updates about new posts, sites I find useful and the occasional rant you can follow me on Twitter. You are also most welcome to subscribe to the RSS-feed.

Joel Abrahamsson

Joel Abrahamsson

I'm a passionate web developer and systems architect living in Stockholm, Sweden. I work as CTO for a large media site and enjoy developing with all technologies, especially .NET, Node.js, and ElasticSearch. Read more

Comments

comments powered by Disqus

More about Software Architecture