Links
Info : Single-responsibility principle - Wikipedia
Info : Open–closed principle - Wikipedia
software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification";[1] that is, such an entity can allow its behaviour to be extended without modifying its source code
Open for Extension:
A class or module should be designed to allow new functionality to be added without modifying its existing code.
Extensions can include adding new methods, properties, or behaviour.
Closed for Modification:
Once a class or module is stable and tested, it should remain unchanged.
Avoid modifying existing code to add new features or alter behaviour.
Violations of the OCP
Consider the following example of a PaymentProcessor
class:
class PaymentProcessor {
processPayment(amount: number, paymentType: string) {
if (paymentType === 'credit') {
console.log(`Processing credit payment of ${amount} using credit`);
} else if (paymentType === 'debit') {
console.log(`Processing debit payment of ${amount} using debit`);
} else if (paymentType === 'cash') {
console.log(`Processing cash payment of ${amount} using cash`);
} else if (paymentType === 'paypal') {
console.log(`Processing PayPal payment of ${amount} using PayPal`);
} else if (paymentType === 'stripe') {
console.log(`Processing Stripe payment of ${amount} using Stripe`);
} else {
console.log(`Invalid payment type ${paymentType}`);
}
}
}
const process = new PaymentProcessor();
process.processPayment(100, 'credit');
process.processPayment(200, 'debit');
process.processPayment(300, 'cash');
In this example:
The
PaymentProcessor
class handles different payment types (credit, debit, cash, PayPal, and Stripe).If a new payment type is introduced, we need to modify the existing class, violating the OCP.
A Better Approach
To adhere to the OCP, we can use interfaces and separate responsibilities. Here’s an improved version:
interface IPaymentProcessor {
processPayment(amount: number): void;
}
class PaymentProcessor {
processor: IPaymentProcessor;
constructor(paymentProcessor: IPaymentProcessor) {
this.processor = paymentProcessor;
}
processPayment(amount: number) {
this.processor.processPayment(amount);
}
}
class CreditCardProcessor implements IPaymentProcessor {
processPayment(amount: number) {
console.log(`Processing credit card payment of ${amount}`);
}
}
class PaypalProcessor implements IPaymentProcessor {
processPayment(amount: number) {
console.log(`Processing PayPal payment of ${amount}`);
}
}
const creditCardProcessor = new CreditCardProcessor();
const paypalProcessor = new PaypalProcessor();
const processor = new PaymentProcessor(creditCardProcessor);
processor.processPayment(100);
In this improved version:
We define an
IPaymentProcessor
interface with aprocessPayment
method.The
PaymentProcessor
class accepts an instance of a payment processor (e.g.,CreditCardProcessor
orPaypalProcessor
).Each payment processor class adheres to the interface and provides its own implementation.
New payment processors can be added without modifying existing code.
By following the OCP, we achieve better maintainability and flexibility in our software systems. Extensions can be added without disrupting existing functionality, making the system more robust and adaptable to change.