Building Your Learning Module...
Getting things ready for you!
Find videos you like?
Save to resource drawer for future reference!
SOLID is an acronym for five design principles that help create maintainable, flexible, and scalable object-oriented code. These principles guide you to write better classes and functions.
A class should have one reason to change
Open for extension, closed for modification
Subtypes must be substitutable for their base types
Many specific interfaces are better than one general interface
Depend on abstractions, not concretions
// Too many responsibilities
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
// Responsibility 1: Data management
save() {
database.insert(this);
}
// Responsibility 2: Email
sendWelcomeEmail() {
emailService.send(this.email, 'Welcome!');
}
// Responsibility 3: Validation
validate() {
return this.email.includes('@');
}
// Responsibility 4: Formatting
toHTML() {
return `<div>${this.name}</div>`;
}
}
// ❌ User class does everything!// Separate responsibilities
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
}
class UserRepository {
save(user) {
database.insert(user);
}
}
class EmailService {
sendWelcome(email) {
emailService.send(email, 'Welcome!');
}
}
class UserValidator {
validate(user) {
return user.email.includes('@');
}
}
class UserFormatter {
toHTML(user) {
return `<div>${user.name}</div>`;
}
}
// ✅ Each class has one job!// Must modify class to add new shapes
class AreaCalculator {
calculate(shapes) {
let total = 0;
for (const shape of shapes) {
if (shape.type === 'circle') {
total += Math.PI * shape.radius ** 2;
} else if (shape.type === 'square') {
total += shape.side ** 2;
} else if (shape.type === 'rectangle') {
total += shape.width * shape.height;
}
// Need to modify this for new shapes!
}
return total;
}
}
// ❌ Adding triangle requires modifying this class// Each shape knows its own area
class Circle {
constructor(radius) {
this.radius = radius;
}
area() {
return Math.PI * this.radius ** 2;
}
}
class Square {
constructor(side) {
this.side = side;
}
area() {
return this.side ** 2;
}
}
class AreaCalculator {
calculate(shapes) {
return shapes.reduce((total, shape) =>
total + shape.area(), 0
);
}
}
// ✅ Add Triangle without modifying calculator!// Bird can fly
class Bird {
fly() {
return 'Flying!';
}
}
// But penguins can't fly!
class Penguin extends Bird {
fly() {
throw new Error("Can't fly!");
}
}
function makeBirdFly(bird) {
return bird.fly(); // Breaks for Penguin!
}
const bird = new Bird();
makeBirdFly(bird); // ✅ Works
const penguin = new Penguin();
makeBirdFly(penguin); // ❌ Throws error!
// Can't substitute Penguin for Bird// Better hierarchy
class Bird {
move() {
return 'Moving!';
}
}
class FlyingBird extends Bird {
fly() {
return 'Flying!';
}
move() {
return this.fly();
}
}
class Penguin extends Bird {
swim() {
return 'Swimming!';
}
move() {
return this.swim();
}
}
function makeBirdMove(bird) {
return bird.move(); // Works for all birds!
}
makeBirdMove(new FlyingBird()); // ✅ Flying!
makeBirdMove(new Penguin()); // ✅ Swimming!
// All birds can be substituted// Fat interface - too many methods
class Worker {
work() { }
eat() { }
sleep() { }
}
// Robot doesn't eat or sleep!
class Robot extends Worker {
work() { return 'Working'; }
eat() { throw new Error('No!'); }
sleep() { throw new Error('No!'); }
}
// ❌ Forced to implement unused methods// Separate interfaces
class Workable {
work() { }
}
class Eatable {
eat() { }
}
class Sleepable {
sleep() { }
}
class Human extends Workable {
// Also implements Eatable, Sleepable
}
class Robot extends Workable {
// Only implements what it needs!
}
// ✅ Each class implements only what it uses// Depends on concrete implementation
class MySQLDatabase {
save(data) { /* MySQL logic */ }
}
class UserService {
constructor() {
this.db = new MySQLDatabase(); // ❌ Tight coupling
}
createUser(user) {
this.db.save(user);
}
}
// Can't switch to PostgreSQL without modifying UserService// Depends on abstraction
class Database {
save(data) { throw new Error('Not implemented'); }
}
class MySQLDatabase extends Database {
save(data) { /* MySQL logic */ }
}
class UserService {
constructor(database) {
this.db = database; // ✅ Injected dependency
}
createUser(user) {
this.db.save(user);
}
}
const service = new UserService(new MySQLDatabase());
// ✅ Easy to swap databases!One class, one job
One reason to change
Open for extension
Closed for modification
Subtypes are substitutable
For their base types
Specific interfaces
Not one general-purpose
Depend on abstractions, not concrete implementations
High-level modules independent of low-level modules