20-04-2025 · rock288

S.O.L.I.D : 5 nguyên tắc của thiết kế hướng đối tượng

✅ S - Single Responsibility Principle (SRP)

Một class hoặc module chỉ nên đảm nhận một vai trò duy nhất trong hệ thống.

📌 Lý do:

Nếu một class có nhiều vai trò, thì bất kỳ thay đổi nào trong một vai trò cũng có thể vô tình ảnh hưởng đến vai trò khác → dễ gây lỗi, khó bảo trì.

❌ Vi phạm:

javascript
class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  save() {
    // lưu vào DB
  }

  sendWelcomeEmail() {
    // gửi email chào mừng
  }
}

✅ Đúng nguyên tắc:

javascript
class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
}

class UserRepository {
  save(user) {
    // lưu vào DB
  }
}

class EmailService {
  sendWelcomeEmail(user) {
    // gửi email
  }
}

✅ O - Open/Closed Principle (OCP)

Module nên cho phép mở rộng (extend) chứ không sửa đổi (modify) mã nguồn sẵn có.

📌 Lý do:

Khi bạn cần thêm tính năng mới, bạn không nên chạm vào logic cũ nếu không cần thiết. Thay vào đó, nên mở rộng bằng cách thêm lớp mới hoặc kế thừa.

❌ Vi phạm:

javascript
function getDiscount(type) {
  if (type === "Gold") return 0.2;
  if (type === "Silver") return 0.1;
  return 0;
}

✅ Đúng nguyên tắc:

javascript
class DiscountPolicy {
  getDiscount() {
    return 0;
  }
}

class GoldPolicy extends DiscountPolicy {
  getDiscount() {
    return 0.2;
  }
}

class SilverPolicy extends DiscountPolicy {
  getDiscount() {
    return 0.1;
  }
}

function applyDiscount(policy) {
  return policy.getDiscount();
}

✅ L - Liskov Substitution Principle (LSP)

Class con phải có thể thay thế class cha mà không làm thay đổi logic chương trình.

📌 Lý do:

Kế thừa phải giữ đúng hành vi gốc. Nếu subclass phá vỡ logic của superclass → không thể thay thế an toàn.

❌ Vi phạm:

javascript
class Bird {
  fly() {
    console.log("I can fly");
  }
}

class Ostrich extends Bird {
  fly() {
    throw new Error("Ostriches can't fly!");
  }
}

✅ Đúng nguyên tắc:

javascript
class Bird {}

class FlyingBird extends Bird {
  fly() {
    console.log("Flying");
  }
}

class Ostrich extends Bird {
  // Không kế thừa phương thức fly()
}

✅ I - Interface Segregation Principle (ISP)

Đừng bắt class phải implement những method mà nó không dùng đến.

(Thay vì tạo một interface "to đùng" với quá nhiều phương thức, ta nên chia nhỏ nó thành nhiều interface chuyên biệt, phù hợp với từng nhu cầu của các class cụ thể.)

❌ Vi phạm:

javascript
class Machine {
  print() {}
  scan() {}
  fax() {}
}

class BasicPrinter extends Machine {
  print() {}
  scan() {
    throw "Not supported";
  }
  fax() {
    throw "Not supported";
  }
}

✅ Đúng nguyên tắc:

javascript
class Printer {
  print() {}
}

class Scanner {
  scan() {}
}

class BasicPrinter extends Printer {
  print() {
    console.log("Printing...");
  }
}

✅ D - Dependency Inversion Principle (DIP)

Module cấp cao không nên phụ thuộc trực tiếp vào module cấp thấp, mà cả hai nên phụ thuộc vào abstraction.

📌 Lý do:

Nếu code phụ thuộc trực tiếp vào implementation cụ thể (ví dụ: MySQL), thì sẽ rất khó test, tái sử dụng hoặc thay đổi (ví dụ: muốn đổi sang MongoDB).

❌ Vi phạm:

javascript
class UserService {
  constructor() {
    this.db = new MySQL();
  }

  save(user) {
    this.db.save(user);
  }
}

✅ Đúng nguyên tắc:

javascript
class UserService {
  constructor(database) {
    this.db = database;
  }

  save(user) {
    this.db.save(user);
  }
}

class MySQL {
  save(data) {
    console.log("MySQL saved:", data);
  }
}

class MongoDB {
  save(data) {
    console.log("Mongo saved:", data);
  }
}

// Inject implementation
const db = new MongoDB();
const service = new UserService(db);
service.save({ name: "Alice" });