Open/Closed Principle (SOLID 2/6)

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification. (Bertrand Meyer in his book Object-Oriented Software Construction)

That is the official definition of open/closed principle (OCP). Another confusing determination; how is it possible to extend our class without modifying it?

Example: Area Calculator

I will use practically the same example as Joel Abrahamsson has represented. I am writing this SOLID series to learn what is SOLID. That was an example which made me understand what is the OCP.

Example That Doesn’t Apply OCP

First, we have a really simple Rectangle class:

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

And another simple class that calculates the sum of rectangles’ area:

public static class AreaCalculator
{
  public static double CalculateAreaSum(Rectangle[] rectangles)
  {
    double areaSum = 0.0;
    foreach (var rectangle in rectangles) {
      areaSum += rectangle.Width * rectangle.Height;
    }
    return areaSum;
  }
}

Why does this simple code violate the OCP? The reason is that it is not closed for modifications nor open for extensions. I will explain why.

If we want to extend the CalculateAreaSum function to work with rectangles and triangles, we have to modify it. Thus it is not closed for modifications if we extend it (and not open for extensions). We’d need to modify it like following:

public static class AreaCalculator
{
  public static double CalculateAreaSum(object[] shapes)
  {
    double areaSum = 0.0;
    foreach (var shape in shapes) {
      if (shape is Rectangle)
        areaSum += shape.Width * shape.Height;
      else  // triangle
        areaSum += shape.Width * shape.Height / 2.0;
    }
    return areaSum;
  }
}

We had to add an if-else clause (rows 9-10) to calculate a sum of areas of rectangles and triangles. So it wasn’t closed for modifications. And if we add more shapes, like circles, we’ll have to modify it again. It is not applying the OCP.

Refactor To Apply OCP

At very first I didn’t realize how this example can be written so that it doesn’t need any modifications. But when I saw the answer it seems even simple (like all good code examples). We will just create a Shape interface:

public interface Shape
{
  double CalculateArea();
}

And then make our shapes to implement it:

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

  public double CalculateArea()
  {
    return Width * Height;
  }
}
// And same kind to Triangle and other shapes.

Now we can refactor the CalculateAreaSum function to receive an array of Shapes:

public static class AreaCalculator
{
  public static double CalculateAreaSum(Shape[] shapes)
  {
    double areaSum = 0.0;
    foreach (var shape in shapes) {
      // We don't have to modify this anymore if we add new shapes!
      areaSum += shape.CalculateArea();
    }
    return areaSum;
  }
}

As we can see, the CalculateAreaSum function can now calculate a sum of “all kind of shapes” and we don’t have to change it. It is now open for extensions because we can add new shapes. And it is closed for modification because we don’t have to modify it when we extend and add new shapes.

Code Against Interfaces, Not Implementations

In the example, practically the trick to applying the OCP was to use an interface rather than concrete classes. This is a good example of “code against interfaces, not implementations” pattern (known also as “program by interface, not by implementation” and “programming to an interface”).

First in the example above we coded against implementation; Rectangle and Triangle classes. We had to put both classes to our CalculateAreaSum function. This violated the OCP.

But then we refactored the CalculateAreaSum function to use the Shape interface. So we coded against the interface and we followed the OCP.

Design Patterns

When we look at the design patterns (originally by “Gang of Four”) we can find several patterns that follow the OCP:

I will explain briefly how strategy pattern is related to the OCP.

The Example and Strategy Pattern

Strategy UML
Class diagram of the strategy pattern.

In one way our example used the strategy pattern. If we change the class and method names to the above class diagram according to our example we will get the following:

Example-as-strategy
Class diagram of the example. Hey, it is a strategy pattern!

As we can see our example follows strategy pattern! To be clear strategy pattern and OCP are not the same thing but as we see they have some kind of relationship. Same is with those other patterns I mentioned before.

Conclusion – What I Learned

You will learn best when you teach (or write a blog post about it). While writing this blog post I have learned deeply:

  • What is the open/closed principle,
  • What “code against interfaces, not implementations” actually means and
  • That strategy pattern and open/closed principle, and few other design patterns have a relationship.
    • And I also learned clearly what is strategy pattern.

I learned a lot; It was worth to write this blog post!

Sources and Influences

Blog Posts in This Series

5 thoughts on “Open/Closed Principle (SOLID 2/6)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s