The basic definition of the principle is that software entities such as classes
, modules
, functions
etc should be open for extension but closed for modification.
Let's break down this statement to understand it in detail.
The statement says that whenever there is a new requirement for a class, we should not be modifying the class but rather extending an abstraction of the class and utilizing it.
Let's take an example to better understand:-
Suppose we create a class for bank accounts where we have two methods to withdraw and deposit the amount.
class BankAccount {
private _customerName: string;
private _customerId: string;
private _amount: number = 10000;
constructor(customerName: string, customerId: string) {
this._customerName = customerName;
this._customerId = customerId;
}
withdrawAmount(newAmount: number) {
this._amount = this._amount - newAmount;
}
depositAmount(newAmount: number) {
this._amount = this._amount + newAmount;
}
}
Now we have a new requirement for another kind of bank account, and for this account, there is some additional fees for withdrawing and depositing amounts.Lets change the code accordingly.
class BankAccount {
private _customerName: string;
private _customerId: string;
private _bankType: string;
private _amount: number = 10000;
constructor(customerName: string, customerId: string, bankType: string) {
this._customerName = customerName;
this._customerId = customerId;
this._bankType = bankType;
}
public get amount(): number {
return this._amount;
}
withdrawAmount(newAmount: number) {
if (this._bankType === "savings") this._amount = this._amount - newAmount;
else if (this._bankType === "current")
this._amount = this._amount - this._amount * 0.005 - newAmount;
}
depositAmount(newAmount: number) {
if (this._bankType === "savings") this._amount = this._amount + newAmount;
else if (this._bankType === "current")
this._amount = this._amount + this._amount * 0.005 + newAmount;
}
}
const savingsAccount = new BankAccount("Manas", "123", "savings");
const currentAccount = new BankAccount("Manas", "123", "current");
savingsAccount.depositAmount(100000);
savingsAccount.withdrawAmount(1212);
console.log("Amount in savings account is ", savingsAccount.amount);
currentAccount.depositAmount(100000);
currentAccount.withdrawAmount(1212);
console.log("Amount in current account is ", currentAccount.amount);
This code works, but can you find the issue with this approach?
Why Open Closed Principle
The above approach has a few issues.
- We will end up testing the whole functionality again since we introduced new code in an already existing feature
- This in turn can end up being a costly process for the organization
- Since our class or method might end up doing multiple things, this also breaks our Single Responsibility Principle
- With the addition of new code, the maintenance overhead for the classes increases.
To get rid of the above issues we use the Open Closed Principle
- We create an abstract class for
bankAccount
and then make thewithdraw()
anddeposit()
method as abstract. - We then consume this abstract class for our respective bank accounts.
- Accordingly we also change the calculation for
deposit
andwithdraw
directly for the account type classes
abstract class BankAccount {
private _customerName: string;
private _customerId: string;
private _bankType: string;
private _amount: number = 10000;
constructor(customerName: string, customerId: string, bankType: string) {
this._customerName = customerName;
this._customerId = customerId;
this._bankType = bankType;
}
public get getAmount(): number {
return this._amount;
}
public set setAmount(amount: number) {
this._amount = amount;
}
abstract withdrawAmount(newAmount: number): void;
abstract depositAmount(newAmount: number): void;
}
class SavingsAccount extends BankAccount {
constructor(customerName: string, customerId: string, bankType: string) {
super(customerName, customerId, bankType);
}
withdrawAmount(newAmount: number): void {
this.setAmount = this.getAmount - newAmount;
}
depositAmount(newAmount: number): void {
this.setAmount = this.getAmount + newAmount;
}
}
class CurrentAccount extends BankAccount {
constructor(customerName: string, customerId: string, bankType: string) {
super(customerName, customerId, bankType);
}
withdrawAmount(newAmount: number): void {
this.setAmount = this.getAmount - this.getAmount * 0.005 - newAmount;
}
depositAmount(newAmount: number): void {
this.setAmount = this.getAmount + this.getAmount * 0.005 + newAmount;
}
}
const savingsAccount = new SavingsAccount("Manas", "123", "savings");
const currentAccount = new CurrentAccount("Manas", "123", "current");
savingsAccount.depositAmount(100000);
savingsAccount.withdrawAmount(1212);
console.log("Amount in savings account is ", savingsAccount.getAmount);
currentAccount.depositAmount(100000);
currentAccount.withdrawAmount(1212);
console.log("Amount in current account is ", currentAccount.getAmount);
From the refactored code we can see that we created new classes based on the accountType
and we ended up updating the withdraw()
and deposit()
functions accordingly. Now if in future we get a new requirement for third type of account we can simply extend the BankAccount
abstract class
Top comments (5)
Great!!
Thanks Sachin
Great explanation!!
Thanks Arjun
Great explanation