This is a continuation of the SOLID series Part 1.
Open/Closed Principle
The open/closed principle states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means that you should be able to add new functionality to a software system by adding new code without having to modify existing code. This can make maintaining and evolving a software system easier over time. It can also help to prevent bugs and other regressions that can arise from modifying existing code.
Explain to a 10-year-old
Let's say you have a toy box with compartments for different toys, like blocks, dolls, and cars. If you want to add a new type of toy to your toy box, like a ball, you shouldn't have to change the toy box itself to do that. Instead, you should be able to open the toy box, put the ball in one of the compartments, and close the toy box again.
Violation
Here is an example of a TypeScript class that violates the open/closed principle,
class Shape {
// Calculate the area of the shape
calculateArea(): number {
// Default implementation
return 0;
}
// Calculate the perimeter of the shape
calculatePerimeter(): number {
// Default implementation
return 0;
}
}
class Circle extends Shape {
// Override calculateArea to provide the correct implementation for a circle
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
}
This code violates the open/closed principle because the Shape class has a default implementation of the calculateArea and calculatePerimeter methods, which means that subclasses must override these methods to provide the correct behavior for each specific type of shape. If we want to add a new shape to the system, we would have to modify the Shape class to add a new method for the new shape.
Fix
To fix this code and adhere to the open/closed principle, we could refactor the Shape class to use abstract methods instead of default implementations. This would allow subclasses to provide their own implementations of the calculateArea and calculatePerimeter methods without requiring us to modify the Shape class:
abstract class Shape {
// Calculate the area of the shape (must be implemented by subclasses)
abstract calculateArea(): number;
// Calculate the perimeter of the shape (must be implemented by subclasses)
abstract calculatePerimeter(): number;
}
class Circle extends Shape {
// Override calculateArea to provide the correct implementation for a circle
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
}
If you don't follow the open/closed principle in your software design, you may run into problems like the following:
Your code may become difficult to maintain and evolve over time. You may introduce bugs if you have to modify existing code to add new functionality.
Your code may become more difficult to understand. If you have to modify existing code to add new functionality, it can make it harder to understand what the code is doing and how it works.
Your code may become more brittle and prone to breaking. If you have to modify existing code to add new functionality, it can make your code more likely to break when you make those modifications. This can lead to more downtime for your software, making it harder to recover from problems when they occur.
How to validate?
To validate whether you have applied the open/closed principle in your software design, you can ask yourself the following questions:
Can I add new functionality to my software without modifying the existing code? If you add new functionality without modifying existing code, you adhere to the open/closed principle. If you have to modify existing code to add new functionality, you do not adhere to the principle.
Are my software entities (classes, modules, functions, etc.) open for extension but closed for modification? The open/closed principle states that software entities should be open for extension, which means that you should be able to extend their functionality without modifying them. They should also be closed for modification, which means that you shouldn't have to modify the entities themselves to add new functionality. If your software entities meet these criteria, you adhere to the principle.
Can I add new functionality to my software system without introducing regression or bugs? If adding new functionality causes problems with the existing code, you do not adhere to the principle.