The Open-Closed principle is a part of SOLID, a mnemonic acronym which bundles a total of 5 design principles.
It is often associated with clean code.
But what exactly is it, is it important to you, should you even care?
What does it state?
The statement it sets is pretty straight-forward:
"Software entities should be open for extension, but closed for modification".
And entities can be a lot:
- classes
- modules
- functions
- etc.
To put it another way:
Entities should allow their behavior to be changed without the need to modify their own source code.
An Example
Take a look at the example below to get an idea of a use case for the open closed principle.
Please keep the following in mind: This is only a very basic example, and there are of course other examples which would also present what this one does well.
const allowedRoles = [“DevOps”, “SRE”];
export function isAllowedToDeployChanges(user) {
return allowedRoles.includes(user.role);
}
The code itself looks okay, but what if you wanted to add or remove a role to/from the allowed ones?
❌ Yes, you'd have to modify your source code.
This is exactly what the open-closed principle tries to prevent!
What does it try to prevent?
OCP tries to prevent us from writing software which is inflexible.
❌ If you had to modify your source code every time you simply wanted to modify some behavior, you wouldn't be able to abstract logic away into libraries!
❌ You would also be very inflexible with your unit tests.
Because as you'd have to modify existing code, you'd also have to add new or modify existing tests each time you make a change!
As you can imagine, this is a pretty inflexible and time-consuming thing to do.
But by enabling your users (even yourself) and giving them the flexibility to extend your logic, you circumvent such scenarios.
✅ Your code becomes more flexible, and you can abstract it away.
✅ And your tests only need to assert the base logic, without any specifics!
Applying the principle
Let's try to fix the example code above and make it conform with the open-closed principle.
If that piece of code should be open for extension, it has to allow users to modify the roles that are allowed to deploy.
const allowedRoles = [“DevOps”, “SRE”];
export function isAllowedToDeployChanges(user) {
return allowedRoles.includes(user.role);
}
export function addAllowedRole(role) {
allowedRoles.push(role);
}
export function removeAllowedRole(role) {
const index = allowedRoles.indexOf(role);
if (index > -1) {
allowedRoles.splice(index, 1);
}
}
As you see, two functions were added.
✅ One to add a new role to the existing ones.
✅ One to remove a role from the existing ones.
All three functions should now make up the API of your module (so better export them), which allows everyone to alter behavior easily!
Should you care?
If you ask me, yes you should!
Following the principle actually saves a lot of time in the long run.
Who likes going back to old code regularly, simply to add a new enumeration or string value, including a modification of existing tests?
And who likes using (library) functionality which is so inflexible that you have to open an issue on GitHub every time you need something a little different?
If you can answer with "Not me" for each of these questions, you know what to do!
Before you leave
If you like my content, visit me on Twitter, and perhaps you’ll like what you see!
Top comments (0)