The Open-Closed Principle states that classes must remain open for extension but closed for modification.
Classes must remain open for extension but closed for modification.
This means that if we want to add more functionality to a class we should do it by extending the behavior of the class without actually changing the class itself. This may seem pretty confusing at first, but will make sense with the code example.
The objective of this principle is to avoid modifications that may lead to bugs where the class is being used.
Example
Let's imagine that we are developing a Checkout functionality for a store that offers discounts to Senior
clients and Pregnant
clients. At first, our base code will be the following:
class Checkout {
getTotalToPay(total, clientType) {
let totalToPay = total;
let discount = 0;
if (clientType === 'Senior') {
discount = total * 0.25;
totalToPay = totalToPay - discount;
} else if (clientType === 'Pregnant') {
discount = total * 0.15;
totalToPay = totalToPay - discount;
}
return totalToPay;
}
}
const checkout = new Checkout();
console.log(checkout.getTotalToPay(100, 'Senior'));
console.log(checkout.getTotalToPay(100, 'Pregnant'));
console.log(checkout.getTotalToPay(100, 'Regular Client'));
By looking at the code you may think that everything is okay, but the reality is we are breaking the Open-Closed Prinicple. This is because, even though the class is open for extension as we may have as many client types as we like, it is not closed for modification.
Let's say that we want to add the Veteran
type of client. To do this, we would simply need to add another else if
to our code. The resulting code will be the following:
class Checkout {
getTotalToPay(total, clientType) {
let totalToPay = total;
let discount = 0;
if (clientType === 'Senior') {
discount = total * 0.25;
totalToPay = totalToPay - discount;
} else if (clientType === 'Pregnant') {
discount = total * 0.15;
totalToPay = totalToPay - discount;
} else if (clientType === 'Veteran') {
discount = total * 0.10;
totalToPay = totalToPay - discount;
}
return totalToPay;
}
}
const checkout = new Checkout();
console.log(checkout.getTotalToPay(100, 'Senior'));
console.log(checkout.getTotalToPay(100, 'Pregnant'));
console.log(checkout.getTotalToPay(100, 'Veteran'));
console.log(checkout.getTotalToPay(100, 'Regular Client'));
Since we had to modify our code to allow this new type of client, we broke the Closed part of the principle.
To adhere to the principle, we may split every type of client into a class and handle the checkout calculation by abstraction. The refactoring of the code would be the following:
class Checkout {
getTotalToPay(total, clientType) {
let discount = clientType.getDiscount(total);
let totalToPay = total - discount;
return totalToPay;
}
}
class SeniorClient {
getDiscount(total) {
return total * 0.25;
}
}
class PregnantClient {
getDiscount(total) {
return total * 0.15;
}
}
class VeteranClient {
getDiscount(total) {
return total * 0.1;
}
}
class RegularClient {
getDiscount(total) {
return total * 0;
}
}
const checkout = new Checkout();
console.log(checkout.getTotalToPay(100, new SeniorClient()));
console.log(checkout.getTotalToPay(100, new PregnantClient()));
console.log(checkout.getTotalToPay(100, new VeteranClient()));
console.log(checkout.getTotalToPay(100, new RegularClient()));
By splitting each client type into different classes, we don't need to touch the Checkout class again. If we wanted to add another client type we would only need to create it as a new class and pass it to the getTotalToPay
function, following the Open-Closed Principle.
Top comments (2)
I followed all the 6 parts and I need to say that you nailed it. You explained the SOLID principles very well by dumbing them down and illustrating them with very clear examples. One thing would make it better IMO would be a quick summary of these principles at the end. But apart from that, this is a great article.
Thanks a lot! I will definitely take your advice for upcoming posts 😁