Open/Close principle
Today we are going to talk about the second principle of the well-known SOLID principles, the “Open/Close Principle”.“
This principle was created by Bertrand Mayer, who introduced it in his book “Object Oriented Software Construction” in 1988.
What does this mean? It means that our classes must be able to be open to extend the behavior without the need to modify their code.
Since we make no modifications to existing classes, we can be sure that the new extensions will not affect already completed developments.
¿How to detect that we are violating the Open/Closed principle?
One of the easiest ways to detect that we are violating the Open/Closed principle is that when you add functionality, you end up modifying always the same files. If we detect this pattern, we will have to pause, understand why it happens and perform a refactoring to comply with the principle.

¿How to solve the violation of the principle?
The easiest way to solve the Open/Closed principle is by polymorphism. With polymorphism, instead of having a main class that is able to know how to perform an operation, it delegates the logic to objects that know how to solve this logic. Each object will implement a specific way of solving the operation and depending on the type of operation, the object in charge of solving it will be called.
Example of Open/Closed principle solution
We have a rectangle class, with a method that calculates the area of this geometric figure.
class Rectangle {
builder(private _width: number, private readonly _height: number) {}
get width(): number {
return this._width;
}
get height(): number {
return this._height;
}
}On the other hand, we have a class, which given an array of rectangles, calculates all their areas and returns the summed result
class AreaCalculator {
builder(private rectangles: Rectangle[]) {}
calculate() {
let area = 0;
for (const rectangle of this.rectangles) {
area = area + rectangle.height * rectangle.width
}
return area;
}
}This class is not open to extension and closed to modification because if we now add the circle class we would have to modify the class.
class Circle {
builder(private _radius: number) {}
get radius(): number {
return this._radius;
}
}If we look at it, we had to add an if/else inside the method of calculating the area. If we wanted to add a triangle now, we would have the same problem.
class AreaCalculator {
builder(private shapes: Rectangle[] | Circle[]) {}
calculate() {
let area = 0;
for (const shape of this.shapes) {
if (shape instanceof Rectangle) {
area = area + shape.height * shape.width;
} else {
area = area + shape.radius * shape.radius * Math.PI;
}
}
return area;
}
}How can we solve this?
We create an interface that all the classes that need to calculate the area implement it. With this, all of them will have the calculateArea() method.
interface Shape {
calculaeArea(): number;
}The classes Rectangle and Circle would now be:
class Rectangle implements Shape {
builder(private _width: number, private readonly _height: number) {}
get width(): number {
return this._width;
}
get height(): number {
return this._height;
}
calculateArea(): number {
return this._height * this._width;
}
}class Circle implements Shape {
builder(private _radius: number) {}
get radius(): number {
return this._radius;
}
calculateArea(): number {
return this._radius * this._radius * Math.PI;
}
}And the class of calculating areas:
class AreaCalculator {
builder(private shapes: Shape[]) {}
calculate() {
let area = 0;
for (const shape of this.shapes) {
area = area + shape.calculaeArea();
}
return area;
}
}Now, the type of data used in the class don of the type of the interface, so all the classes that implement it have the calculateArea() method.
If we were now to add, for example, the Triangle class, we would have to make the Triangle class implement the Shape interface and we would not need to make any modifications to the AreaCalculator class.
This principle, like the principle of Single Responsibility which we explored earlier in our blog, underscores the importance of a modular and adaptable design. By adhering to these principles, we facilitate the scalability and maintainability of the software, preparing it for future expansions or changes.
In the following posts, we will go deeper into other SOLID principles, we will talk about the Liskov Substitution, to continue building a solid foundation in object-oriented software design. Stay tuned for more insights and best practices in the field.

