Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

SOLID Principles: The Open Closed Principle -> What, Why and How

4.98/5 (20 votes)
29 Jun 2013CPOL3 min read 72.4K  
SOLID principles: The Open Closed Principle, a simple example in C#

Introduction

This article will give an explanation of the Open Closed Principle (OCP) and will show a simple example in C#.

Background

What

In 1988, the open closed principle (OCP) was already mentioned by Bertrand Meyer:
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
"Object Oriented Software Construction, Bertrand Meyer"
Software that works should when possible not be changed when your application is extended with new functionality.
Instead, it should be possible to extend the existing software with new functionality without any modification to the current codebase and without adding duplicate code or duplicate functionality.

Why

Tell me how many times you create a green field application from scratch versus the number of times you are working on an existing codebase just adding new functionality?
Chances are good that you spend far more time adding new features to an existing codebase right ?
Then ask yourself the question: is it easier to write all new code or to make changes to existing code ?
The OCP handles the way you want to structure your code in order to yield one of the greatest benefits claimed for object oriented technology: reusability and maintainability.

How

The best way to implement the open closed principle is to first start with implementing the Single Responsibility Principle: a class should have one, and only one, reason to change.
This will separate different concerns in your code.
The next step is represent these separate concerns by abstractions and let consumers of these concerns talk to these abstractions.

To state the open closed principle in a very straightforward way, you can say:

  • You should design modules that never change.
  • When requirements change, you extend the behavior of such modules by adding new code, not by changing old code that already works.

Abstraction is the way to realize this principle.
Derivatives from an abstraction are closed for modification because the abstraction is fixed but behaviour can be extended by creating new derivatives of the abstraction.

Using the Code

The following example shows a class that calculates the sum of areas of an array of shapes.
When a new shape is introduced, two things must be implemented:

  • The shape class itself.
  • The AreaCalculator must be changed to calculate the area of this new shape.

The AreaCalculator violates the open closed principle because, a new requirement for this code will always consist of a change in calculation of a certain existing shape or a new shape is introduced.
In both cases, the AreaCalculator has to be changed and is therefore not closed for modification.
It is also not open for extension because we cannot extend the functionality of the AreaCalulator without modifying it.

Problem

C#
/***************** Calculates the sum of all shape area's *****************/

public class AreaCalculator
{ 
 public double Area(object[] shapes)
 {
  double area = 0;

  foreach (var shape in shapes)
  {  
   if (shape is Square)
   {
     Square square = (Square)shape;
     area += Math.Sqrt(square.Height);
   }

   if (shape is Triangle)
   {
     Triangle triangle = (Triangle)shape;
     double TotalHalf = (triangle.FirstSide + triangle.SecondSide + triangle.ThirdSide) / 2;
     area += Math.Sqrt(TotalHalf * (TotalHalf - triangle.FirstSide) * 
     (TotalHalf - triangle.SecondSide) * (TotalHalf - triangle.ThirdSide));
   }

   if (shape is Circle)
   {
     Circle circle = (Circle)shape;
     area += circle.Radius * circle.Radius * Math.PI;
   }

  }
  return area;
 }
}
C#
public class Square
{
  public double Height { get; set; }
}
C#
public class Circle
{
  public double Radius { get; set; }
}
C#
public class Triangle
{
  public double FirstSide { get; set; }
  public double SecondSide { get; set; }
  public double ThirdSide { get; set; }
}

Following the solution.

Step 1: Separate responsibilities by moving the area calculations to the concerned shape classes.
Step 2: Introduce abstractions of the concerned functionality and let consumers obey these abstractions.

Now when a new shape is introduced, or e.g., the area calculation of a certain shape changes, the AreaCalculator does not have to change and it can easily be extended with new shapes without the need for change.

A footnote is that real closure many times only can be realized in theory because there is always a change possible that will violate the closure. If for example, the sequence of the area calculation must be changed, then we have to change the AreaCalculator. That’s why the closure must be chosen wisely based on feeling and experience.

Solution

Open Closed AreaCalculator:

C#
/***************** Calculates the sum of all shape area's *****************/

public class AreaCalculator
{
  public double Area(Shape[] shapes)
  {
    double area = 0;

    foreach (var shape in shapes)
    {
     area += shape.Area();
    }

     return area;
   }
}

Introduce the abstraction:

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

Square class implements the abstraction and its area calculation:

C#
public class Square : Shape
{
 public double Height { get { return _height; } }
 private double _height; 

 public Square(double Height)
 {
   _height = Height;
 }

 public override double Area()
 {
   return Math.Sqrt(_height);
 }
}

Circle class implements the abstraction and its area calculation:

C#
public class Circle : Shape
{
  public double Radius{get{return _radius;}}
 
  private double _radius;

  public Circle(double Radius)
  {
    _radius = Radius;
  }

  public override double Area()
  {
   return _radius * _radius * Math.PI;
  }
}

Triangle class implements the abstraction and its area calculation:

C#
public class Triangle : Shape
{ 
 public double FirstSide {get {return _firstSide;}}
 public double SecondSide { get { return _secondSide; } }
 public double ThirdSide { get { return _thirdSide; } } 
 
 private double _firstSide;
 private double _secondSide;
 private double _thirdSide; 
 
 public Triangle(double FirstSide, double SecondSide, double ThirdSide)
 {
   _firstSide = FirstSide;
   _secondSide = SecondSide;
   _thirdSide = ThirdSide;
 }

 public override double Area()
 {
   double TotalHalf = (_firstSide + _secondSide + _thirdSide) / 2;
   return Math.Sqrt(TotalHalf * (TotalHalf - _firstSide) * 
                   (TotalHalf - _secondSide) * (TotalHalf - _thirdSide));
 }
}

Have fun!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)