Open-Closed principle

C# Jun 28, 2021
Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification

This principle advises us to refactor the system so that further changes are achieved by adding new code and, because it doesn’t change the old code, will not cause more modifications. Modules that conform to OCP have two primary attributes.

  1. They are open for extension. The behavior of the module can be extended. As the application change requirements, we can develop the module with new actions that satisfy those changes. In other words, we can change what the module does.
  2. They are closed for modification. Extending the module’s behavior does not result in changes to the module’s source or binary code. The binary executable version of the module, whether in a linkable library, a DLL, or a.EXE file remains untouched.

To achieve the goal of modifying a module’s behaviors without changing its source code, we have to introduce the abstractions (abstract base classes that represent an unbounded group of possible actions represented by all the possible derivative classes).

A module can manipulate an abstraction. Such a module can be closed for modification since it depends on an abstraction that is fixed. Yet the behavior of that module can be extended by creating new derivatives of the abstraction.

For example, consider the following classes that derive from the same base class Shape. The function DrawAllShapes walks an array of shapes, examining the type and then calling the appropriate function, either DrawCircle or DrawSquare.

public enum ShapeType
{
    circle,
    square
}
public struct Point
{
    public double x;
    public double y;
}
public class Shape
{
    public ShapeType Type;
}
public class Circle : Shape
{
    public double Radius { get; set; }
    public Point Center { get; set; }
}
public class Square : Shape
{
    public double Side { get; set; }
    public Point TopLeft { get; set; }
}
public void DrawCircle(Circle circle)
{
    // draw circle
}
public void DrawSquare(Square square)
{
    // draw square
}
public void DrawAllShapes(Shape[] shapes)
{
    int i;
    for (i = 0; i < shapes.Length; i++)
    {
        Shape shape = shapes[i];
        switch (shape.Type)
        {
            case ShapeType.square:
                DrawSquare((Square)shape);
                break;
            case ShapeType.circle:
                DrawCircle((Circle)shape);
                break;
        }
    }
}

If I want to extend this function to draw a list of shapes that include triangles, I would have to modify this function by adding a new member to the ShapeType enum. Since all the different shapes depend on this enum’s declaration, I’d have to recompile them. So, I not only must change the source code of all switch/case statements or if/ else chains but also alter the binary files, via recompilation, of all the modules that use any of the Shape classes. For this reason, it cannot be closed against new modifications, and the function DrawAllShapes doesn’t conform to OCP.

A solution to the square/circle problem that conforms to OCP is to write an abstract class named Shape. This abstract class has a single abstract method called Draw. Both Circle and Square are derivatives of the Shape class.

public interface Shape
{
  void Draw();
}
public class Square : Shape
{
  public void Draw()
  {
    //draw a square
  }
}
public class Circle : Shape
{
  public void Draw()
  {  
    //draw a circle
  }
}
public void DrawAllShapes(IList shapes)
{
  foreach(Shape shape in shapes)
  {
    shape.Draw();
  }
}

In this case, if we want to extend the behavior of the DrawAllShapes to draw a new kind of shape, all we need do is add a new derivative of the Shape class. The DrawAllShapes function does not need to change. Thus, DrawAllShapes conforms to OCP. Its behavior can be extended without modifying it. Indeed, adding a triangle class has no effect on any of the modules shown here.

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.