How to Apply the Open/Closed Principle in TypeScript
Expanding existing functionality in software can become a challenge if the system is not designed to accommodate changes efficiently. The Open/Closed Principle (OCP), a core concept of the SOLID principles in object-oriented programming, offers a solution by advocating for designs that are open to extensions but closed to modifications. This ensures ease of scalability while preserving existing functionality.
In this article, we’ll explore the Open/Closed Principle, highlight its importance, and demonstrate its application using the Strategy Pattern.
Understanding the Open/Closed Principle
The Open/Closed Principle, introduced by Bertrand Meyer, emphasizes:
- Open to Extension: New functionality should be added without altering the existing code.
- Closed to Modification: Existing code should not need to be changed when extending behavior.
This principle ensures that systems remain robust and maintainable, even as new requirements emerge.
A Problematic Example: Violating the Open/Closed Principle
Consider a basic calculator function that performs operations like addition and subtraction. Using a switch
statement to handle operations violates the Open/Closed Principle because every new operation requires changes to the existing function:
1 function calculate(operand1: number, operand2: number, operation: string): number {2 switch (operation) {3 case "add":4 return operand1 + operand2;5 case "subtract":6 return operand1 - operand2;7 default:8 throw new Error("Unsupported operation");9 }10 }
Why It’s Problematic:
Adding a new operation, such as multiplication, requires modifying the calculate function. This approach tightly couples the logic and makes the code harder to maintain or test.
A Better Approach: Following the Open/Closed Principle
Using the Strategy Pattern, we can design the calculator in a way that supports new operations without modifying existing code.
Implementation
1. Define a Strategy Interface
Create an interface for calculation strategies.
1 interface CalculationStrategy {2 execute(operand1: number, operand2: number): number;3 }
2. Implement Strategy Classes
Define classes for specific operations like addition and subtraction.
1 class AddStrategy implements CalculationStrategy {2 execute(operand1: number, operand2: number): number {3 return operand1 + operand2;4 }5 }67 class SubtractStrategy implements CalculationStrategy {8 execute(operand1: number, operand2: number): number {9 return operand1 - operand2;10 }11 }
3. Create a Calculator Class
Use the strategy interface within the Calculator
class to perform operations.
1 class Calculator {2 private strategy: CalculationStrategy;34 constructor(strategy: CalculationStrategy) {5 this.strategy = strategy;6 }78 setStrategy(strategy: CalculationStrategy) {9 this.strategy = strategy;10 }1112 calculate(operand1: number, operand2: number): number {13 return this.strategy.execute(operand1, operand2);14 }15 }
4. Adding New Operations
When a new operation is required, simply create a new strategy class without altering the existing code. For example:
1 class MultiplyStrategy implements CalculationStrategy {2 execute(operand1: number, operand2: number): number {3 return operand1 * operand2;4 }5 }
5. Usage Example
1 const calculator = new Calculator(new AddStrategy());2 console.log(calculator.calculate(5, 3)); // Output: 834 calculator.setStrategy(new SubtractStrategy());5 console.log(calculator.calculate(5, 3)); // Output: 267 calculator.setStrategy(new MultiplyStrategy());8 console.log(calculator.calculate(5, 3)); // Output: 15
Benefits of Following the Open/Closed Principle
- Extensibility: New features can be added with minimal effort.
- Maintainability: Existing code remains unchanged, reducing the risk of introducing bugs.
- Reusability: Strategy classes can be reused across different projects.
Conclusion
The Open/Closed Principle promotes robust, scalable, and maintainable software design. By leveraging patterns like the Strategy Pattern, you can design systems that adapt easily to new requirements without modifying existing functionality. Implementing this principle ensures your code remains future-proof, reducing development overhead and enhancing reliability.
Start applying the Open/Closed Principle today to create cleaner, more modular code!