Skip to content

SOLID Principles

SOLID is an acronym for five fundamental principles of object-oriented design. These principles guide developers to create code that is maintainable, scalable, and flexible.

The term was coined by Robert C. Martin (Uncle Bob) in the early 2000s.

LetterPrincipleCore Idea
SSingle ResponsibilityA class should have one reason to change
OOpen/ClosedOpen for extension, closed for modification
LLiskov SubstitutionSubtypes must be substitutable for their base types
IInterface SegregationMany specific interfaces are better than one general interface
DDependency InversionDepend on abstractions, not concrete implementations

graph TD
SOLID["SOLID Principles"]
SOLID --> S["S — Single Responsibility<br/>One job per class"]
SOLID --> O["O — Open/Closed<br/>Extend, don't modify"]
SOLID --> L["L — Liskov Substitution<br/>Substitute freely"]
SOLID --> I["I — Interface Segregation<br/>Small, focused interfaces"]
SOLID --> D["D — Dependency Inversion<br/>Depend on abstractions"]

S — Single Responsibility Principle (SRP)

Section titled “S — Single Responsibility Principle (SRP)”

A class should have only one reason to change.

// ❌ Violates SRP — three responsibilities
class ReportManager {
generateReport(data) { /* ... */ }
printReport(report) { /* ... */ }
emailReport(report) { /* ... */ }
}
// ✅ Follows SRP
class ReportGenerator { generate(data) { /* ... */ } }
class ReportPrinter { print(report) { /* ... */ } }
class EmailService { send(to, report) { /* ... */ } }

Classes should be open for extension but closed for modification.

// ❌ Violates OCP — adding new shape requires modification
class AreaCalculator {
calculate(shape) {
if (shape.type === "circle") { return Math.PI * shape.radius ** 2; }
else if (shape.type === "rectangle") { return shape.width * shape.height; }
// Need to add more 'else if' for new shapes!
}
}
// ✅ Follows OCP
class Shape { area() { return 0; } }
class Circle extends Shape { area() { return Math.PI * this.radius ** 2; } }
class Rectangle extends Shape { area() { return this.w * this.h; } }
// New shape — no existing code modified!
class Triangle extends Shape { area() { return (this.b * this.h) / 2; } }

Subtypes must be substitutable for their base types.

class Rectangle {
setWidth(w) { this.width = w; }
setHeight(h) { this.height = h; }
area() { return this.width * this.height; }
}
class Square extends Rectangle {
setWidth(w) { this.width = w; this.height = w; } // Changes behavior!
setHeight(h) { this.width = h; this.height = h; } // Changes behavior!
}

Separate interfaces for different shapes — don’t force Square to inherit from Rectangle.


I — Interface Segregation Principle (ISP)

Section titled “I — Interface Segregation Principle (ISP)”

Many specific interfaces are better than one general interface.

class MultiFunctionDevice {
print() { throw "Not implemented"; }
scan() { throw "Not implemented"; }
fax() { throw "Not implemented"; }
}
class Printer { print() { /* ... */ } }
class Scanner { scan() { /* ... */ } }
class SimplePrinter extends Printer { print() { /* works */ } }

D — Dependency Inversion Principle (DIP)

Section titled “D — Dependency Inversion Principle (DIP)”

Depend on abstractions, not concrete implementations.

class UserService {
constructor() {
this.db = new MySQLDatabase(); // Direct dependency!
}
}
class UserService {
constructor(database) {
this.db = database; // Depends on abstraction
}
}

PrincipleBenefit
SRPEasier to understand, test, and maintain
OCPAdd features without breaking existing code
LSPInheritance hierarchies are reliable
ISPClasses aren’t forced to implement unused methods
DIPLoose coupling, easy to swap implementations

PrincipleOne-Liner
SOne class = one job
OExtend without modifying
LDon’t break parent contracts
ISmall, focused interfaces
DDepend on abstractions

Previous Topic: Design Principles → Next Topic: Design Principles (DRY, KISS, YAGNI) →