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)