Open/Closed Principle (OCP)
Add new behavior by extending, not by editing — existing tested code stays untouched.
Intent & Description
🎯 Intent
Adding new features by modifying existing code risks breaking what already works. The fix is to make existing code closed to modification but open to extension through abstractions.
📋 Context
Every new feature or variant means editing tested code, growing if-else chains, and adding switch cases. Each edit is a regression risk. New shapes, payment types, notification channels — all require changing the same class.
💡 Solution
Design around abstractions — interfaces or abstract classes. New functionality arrives as a new implementation of an existing abstraction, not as an edit to the class. Add a Triangle by writing a new Triangle class, not by touching ShapeCalculator.
Real-world Use Case
📌 TL;DR
Extend by adding, not by editing. New shapes, types, rules — new classes. Never a modification to old ones.
Advantages
- Stable, tested code is never touched when adding new cases
- Regression risk drops sharply — existing tests keep passing
- Naturally supports plugin and extension architectures
- New contributors can add features without understanding the whole system
Disadvantages
- Requires upfront abstraction investment — wrong abstractions are harder to reverse than if-else chains
- Over-engineered for truly one-off cases that will never vary
- More classes; more files to navigate
// Before: Violates OCP
class ShapeCalculator {
calculateArea(shape) {
if (shape.type === 'circle') {
return Math.PI * shape.radius ** 2;
} else if (shape.type === 'rectangle') {
return shape.width * shape.height;
}
// Add new shapes here (modification)
}
}
// After: OCP applied
class Shape {
calculateArea() {
throw new Error('Must implement calculateArea');
}
}
class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}
calculateArea() {
return Math.PI * this.radius ** 2;
}
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
calculateArea() {
return this.width * this.height;
}
}
// New shapes can be added without modifying existing code (extension)