JavaScript Development Space

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:

  1. Open to Extension: New functionality should be added without altering the existing code.
  2. 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:

ts
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.

ts
1 interface CalculationStrategy {
2 execute(operand1: number, operand2: number): number;
3 }

2. Implement Strategy Classes

Define classes for specific operations like addition and subtraction.

ts
1 class AddStrategy implements CalculationStrategy {
2 execute(operand1: number, operand2: number): number {
3 return operand1 + operand2;
4 }
5 }
6
7 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.

ts
1 class Calculator {
2 private strategy: CalculationStrategy;
3
4 constructor(strategy: CalculationStrategy) {
5 this.strategy = strategy;
6 }
7
8 setStrategy(strategy: CalculationStrategy) {
9 this.strategy = strategy;
10 }
11
12 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:

ts
1 class MultiplyStrategy implements CalculationStrategy {
2 execute(operand1: number, operand2: number): number {
3 return operand1 * operand2;
4 }
5 }

5. Usage Example

ts
1 const calculator = new Calculator(new AddStrategy());
2 console.log(calculator.calculate(5, 3)); // Output: 8
3
4 calculator.setStrategy(new SubtractStrategy());
5 console.log(calculator.calculate(5, 3)); // Output: 2
6
7 calculator.setStrategy(new MultiplyStrategy());
8 console.log(calculator.calculate(5, 3)); // Output: 15

Benefits of Following the Open/Closed Principle

  1. Extensibility: New features can be added with minimal effort.
  2. Maintainability: Existing code remains unchanged, reducing the risk of introducing bugs.
  3. 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!

JavaScript Development Space

© 2024 JavaScript Development Space - Master JS and NodeJS. All rights reserved.