SOLID CODE: Open/Closed Principle

Beribey
5 min readDec 31, 2020

To become a senior among millions of programmers!

Photo by Miti on Unsplash

Introduction

This is the 2nd article in the SOLID series for hard-coded youths. This article will talk about the Open/Closed Principle — The Principle of Open and Close.

  1. Single Responsibility Principle
  2. Open / Closed Principle
  3. Liskov Substitution Principle
  4. Interface Segregation Principle
  5. Dependency Inversion Principle

Explain the principle

According to this principle, a module should meet the following 2 conditions:

  • Easy to expand: Can easily upgrade, expand, add new features to a module when required.
  • Difficult to modify: Limiting or prohibiting modifying the source code of an existing module.

Do these two conditions sound too contradictory at first? Normally, when you want to add functionality, you have to write more code or edit the existing code. Over here, the principle restricts modifying the source code !! So how can we design a module that is easy to expand but difficult to modify?

Before we talk about programming, let’s analyze an object that’s well-designed according to the Open-Off Principle: the gun. To shoot further, we can attach more scopes; In order not to make a sound, you can attach a silencer barrel; to increase the number of bullets, one can attach additional magazines; When we need melee combat, we can attach bayonets too.

Visibly, the gun is designed to easily expand functionality without dissecting and disassembling its internal parts (source code). A suitable OCP module should also be designed in the same way.

Photo by steve woods on Unsplash

Example

Let’s read the code in the example below:

// We have 3 classes: square, circle, triangle, inherit the class Shape
public class Shape
{
}
public class Square: Shape
{
public double Height {get; set; }
}
public class Circle: Shape
{
public double Radius {get; set; }
}
public class Triangle: Shape
{
public double FirstSide {get; set; }
public double SecondSide {get; set; }
public double ThirdSide {get; set; }
}

// Module print out the area of ​​the figure
public class AreaDisplay
{
public double ShowArea (List <Shape> shapes)
{
foreach (var shape in shapes) {
// If requested to change, add another shape, we have to modify the Area Calculator module
if (shape is Square) {
Square square = (Square) shape;
var area + = Math.Sqrt (square.Height);
Console.WriteLine (area);
}
if (shape is Triangle) {
Triangle triangle = (Triangle) shape;
double TotalHalf = (triangle.FirstSide + triangle.SecondSide + triangle.ThirdSide) / 2;
var area + = Math.Sqrt (TotalHalf * (TotalHalf - triangle.FirstSide) *
(TotalHalf - triangle.SecondSide) * (TotalHalf - triangle.ThirdSide));
Console.WriteLine (area);
}
if (shape is Circle) {
Circle circle = (Circle) shape;
var area + = circle.Radius * circle.Radius * Math.PI;
Console.WriteLine (area);
}
}
}
}

We have 3 classes: Square, Circle, Rectangle. Class AreaDisplay calculates the area of these shapes and prints them. According to the old code, to calculate the area, we need to use the if function to check and force the input object, then start calculating. It is easy to see if we add more classes in the future, edit the AreaDisplay class, and write more if the if function. After editing, we have to compile and deploy the AreaDisplay class, long and error-prone again, right ??

Applying OCP, we will improve as follows:

// We have 3 classes: square, circle, triangle, inherit the class Shape
// Convert logic to calculate the area into each class
public abstract class Shape
{
public double Area ();
}
public class Square: Shape
{
public double Height {get; set; }
public double Area () {
return Math.Sqrt (this.Height);
}
}
public class Circle: Shape
{
public double Radius {get; set; }
public double Area () {
return this.Radius * this.Radius * Math.PI;
}
}
public class Triangle: Shape
{
public double FirstSide {get; set; }
public double SecondSide {get; set; }
public double ThirdSide {get; set; }
public double Area () {
double TotalHalf = (this.FirstSide + this.SecondSide + this.ThirdSide) / 2;
var area + = Math.Sqrt (TotalHalf * (TotalHalf - this.FirstSide) *
(TotalHalf - this.SecondSide) * (TotalHalf - this.ThirdSide));
return area;
}
}

// Module print out the area of ​​the figure
public class AreaDisplay
{
public double ShowArea (List <Shape> shapes)
{
foreach (var shape in shapes) {
Console.WriteLine (shape.Area ());
}
}
}

We move the module area into each class. Class AreaDisplay, just print it out. In the future, when adding a new class, we need to let this class inherit the original Shape class. The AreaDisplay class can print the area of ​​additional classes without needing to modify its source code.

Conclusion

The OCP principle appears in every corner of the programming industry. One application of this principle is the plug-in system (for Eclipse, Visual Studio, Chrome add-on). To add new features to the software, you need to install these plug-ins, no need to interfere with the existing source code.

This principle also applies tightly when writing frameworks/libraries. For example, when using MVC, we cannot view or modify the existing Router and Controller classes' code, but we can write new Controllers, New Routers, to add functionality. Using ionic-framework, we can download an additional plug-in to use the barcode capture and scan functions without touching the ionic’s source code.

When applying this principle in design, we need to identify things that need to be changed to be consistent with that change. This is a very tricky job, even for longtime developers. It requires a lot of experience, vision, and a little bit of prediction. What if I mistakenly guess what needs to change? Just write the code to run first and then refactor.

References

--

--

Beribey

Always be nice to anybody who has access to my toothbrush.