๐ ๐จ๐ฎ๐ง๐๐๐ญ๐ข๐จ๐ง๐ฌ ๐จ๐ ๐๐๐ฃ๐๐๐ญ-๐๐ซ๐ข๐๐ง๐ญ๐๐ ๐๐ซ๐จ๐ ๐ซ๐๐ฆ๐ฆ๐ข๐ง๐
1. ๐๐ข๐ง๐ ๐ฅ๐ ๐๐๐ฌ๐ฉ๐จ๐ง๐ฌ๐ข๐๐ข๐ฅ๐ข๐ญ๐ฒ ๐๐ซ๐ข๐ง๐๐ข๐ฉ๐ฅ๐ (๐๐๐):
Before SRP :
class User {
constructor(name,email) {
this.name = name;
this.email = email;
}
getUserInfo() {
return `Name: ${this.name}, Email: ${this.email}`;
}
saveUserToDatabase() {
// Code to save user to database
console.log('User saved to DB.');
}
}
After SRP :
class User {
constructor(name,email) {
this.name = name;
this.email = email;
}
getUserInfo() {
return `Name: ${this.name}, Email: ${this.email}`;
}
}
class UserRepository {
save(User) {
console.log('User saved to DB.');
}
}
const user = new User('John Doe', 'johndoe@example.com');
const userRepository = new UserRepository();
userRepository.save(user);
2. ๐๐ฉ๐๐ง/๐๐ฅ๐จ๐ฌ๐๐ ๐๐ซ๐ข๐ง๐๐ข๐ฉ๐ฅ๐ (๐๐๐):
Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
Before ๐๐๐ :
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
}
class Circle {
constructor(radius) {
this.radius = radius;
}
}
function calculateArea(shape) {
if (shape instanceof Rectangle) {
return shape.width * shape.height;
} else if (shape instanceof Circle) {
return Math.PI * shape.radtus * shape.radius;
}
// Imagine adding more shapes here would require modifying this function
After ๐๐๐ :
class Shape {
area() {
throw new Error("This method must be ovirridden!");
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
area() {
return this.width * this.height;
}
}
class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}
area() {
return Math.PI * this.radius * this.radius;
}
}
const shapes = [
new Rectangle(10, 20),
new Circle(5),
];
3. ๐๐ข๐ฌ๐ค๐จ๐ฏ ๐๐ฎ๐๐ฌ๐ญ๐ข๐ญ๐ฎ๐ญ๐ข๐จ๐ง ๐๐ซ๐ข๐ง๐๐ข๐ฉ๐ฅ๐ (๐๐๐):
Objects of a derived class should be able to replace objects of the base class without altering the correctness of the program.
Before ๐๐๐ :
class Bird {
fly() {
console.log('Flying');
}
}
class Ostrich extends Bird {
fly() {
throw new Error('Ostriches cannot fly');
}
}
function makeBirdFly(bird) {
bird.fly();
}
const ostrich = new Ostrich();
makeBirdFly(ostrich); // Error: Ostriches cannot fly
After ๐๐๐ :
class Bird {
move() {
console.log('Moving');
}
}
class FlyingBird extends Bird {
fly() {
console.log('Flying');
}
}
class Ostrich extends Bird {
move() {
console.log('Running');
}
}
function makeBirdMove(bird) {
bird.move();
}
const ostrich = new Ostrich();
makeBirdMove(ostrich); //Running
4. ๐๐ง๐ญ๐๐ซ๐๐๐๐ ๐๐๐ ๐ซ๐๐ ๐๐ญ๐ข๐จ๐ง ๐๐ซ๐ข๐ง๐๐ข๐ฉ๐ฅ๐ (๐๐๐):
Clients should not be forced to depend on interfaces they do not use.
Before ISP :
class Worker {
construct(name) {
this.name = name;
}
}
work() {
console.log(`${this.name} is working`)
}
eat() {
console.log(`${this.name} is eating`)
}
class Robot extends Worker {
eat() {
throw new Error('Robots do not eat');
}
}
const robot = new Robot('robo');
robot.eat(); // Error: Robots do not eat
After ISP :
class Worker {
construct(name) {
this.name = name;
}
}
class HumanWorker extends Worker {
work() {
console.log(`${this.name} is working`)
}
eat() {
console.log(`${this.name} is eating`)
}
}
class Robot extends Worker {
work() {
console.log(`${this.name} is working`)
}
}
const robot = new Robot('robo');
robot.work(); // Robot is working
5. ๐๐๐ฉ๐๐ง๐๐๐ง๐๐ฒ ๐๐ง๐ฏ๐๐ซ๐ฌ๐ข๐จ๐ง ๐๐ซ๐ข๐ง๐๐ข๐ฉ๐ฅ๐ (๐๐๐):
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Before DIP :
The Notification class is directly coupled to the EmailService class. If you want to use another type of notification service (such as SMS), you need to modify the Notification class.
class EmailService {
send(message) {
console.log(`Sending email: ${message}`)
}
}
class Notification {
constructor() {
this.emailService = new EmailService();
}
sendNotification(message) {
this.emailService.send(message);
}
}
const notification = new Notification();
notification.sendNotification('Hello, this is a notification');
After DIP :
The Notification class depends on the abstraction NotificationService instead of concrete implementations. You can inject any implementation of NotificationService (such as EmailService or SMSService) without modifying the Notification class. This makes the code more flexible and extensible.
// Abstraction (interface)
class NotificationService {
send(message) {
throw new Error('This method must be overriden!')
}
}
class EmailService extends NotificationService {
send(message) {
console.log(`Sending email: ${message}`)
}
}
class SMSService extends NotificationService {
send(message) {
console.log(`Sending SMS: ${message}`)
}
}
class Notification {
constructor(notificationService) {
this.notificationService = notificationService();
}
sendNotification(message) {
this.notificationService.send(message);
}
}
}
const emailService = new EmailService();
const smsService = new SMSService();
const emailNotification = new Notification(emailService);
emailNotification.sendNotification('Hello, this is an email notification');
const smsNotification = new Notification(smsService);
smsNotification.sendNotification('Hello, this is an SMS notification');