SOLID – 5 nguyên lý nền tảng để viết code rõ ràng và dễ bảo trì

Để khắc phục điều đó, bộ nguyên lý SOLID được đề xuất bởi Robert C. Martin (Uncle Bob) nhằm giúp lập trình viên xây dựng hệ thống dễ mở rộng, dễ sửa chữa và dễ kiểm thử.

 

Áp dụng SOLID sẽ giúp:

 

  • Tăng tính rõ ràng và cấu trúc cho mã nguồn

 

  • Giảm sự phụ thuộc không cần thiết giữa các phần

 

  • Dễ dàng bảo trì và mở rộng tính năng mới

 

1. Single Responsibility Principle – Một lý do duy nhất để thay đổi

 

Mỗi class nên chỉ có một nhiệm vụ chính. Nếu một class đảm nhận nhiều việc, việc thay đổi một chức năng có thể làm ảnh hưởng tới các chức năng khác.

 

Ví dụ tốt:

 

// Tách rõ trách nhiệm

class InvoicePrinter {

  print(invoice: Invoice) { /* logic in ra hoá đơn */ }

}


class InvoiceSaver {

  save(invoice: Invoice) { /* logic lưu hoá đơn vào DB */ }

}

 

 

Ví dụ chưa tốt:

 

// Gộp quá nhiều chức năng

class InvoiceHandler {

  print(invoice: Invoice) { ... }

  save(invoice: Invoice) { ... }

  sendEmail(invoice: Invoice) { ... }

}

 

 

Ở ví dụ sau, class InvoiceHandler gộp nhiều trách nhiệm, khiến nó trở nên khó bảo trì.

 

2. Open/Closed Principle – Mở để mở rộng, đóng để chỉnh sửa

 

Thay vì thay đổi code cũ khi có tính năng mới, hãy thiết kế sao cho có thể mở rộng mà không ảnh hưởng đến phần hiện có.

 

Ví dụ:

 

interface PaymentMethod {

  pay(amount: number): void;

}


class CreditCard implements PaymentMethod {

  pay(amount: number) { console.log("Paid by credit card"); }

}


class Paypal implements PaymentMethod {

  pay(amount: number) { console.log("Paid by Paypal"); }

}


function checkout(method: PaymentMethod) {

  method.pay(100);

}

 

Khi muốn thêm phương thức thanh toán khác, chỉ cần viết thêm class mới mà không cần thay đổi hàm checkout.

 

3. Liskov Substitution Principle – Thay thế mà không phá vỡ

 

Class con cần tuân thủ hành vi của class cha để có thể được sử dụng thay thế mà không làm sai lệch kết quả mong đợi.

 

Ví dụ chưa đúng:


 
class Rectangle {

  setWidth(w: number) { ... }

  setHeight(h: number) { ... }

}


class Square extends Rectangle {

  setWidth(w: number) {

    this.setHeight(w); // Thay đổi hành vi gốc

  }

}

 

4. Interface Segregation Principle – Chỉ sử dụng đúng thứ cần

 

Đừng ép các class phải triển khai những phương thức không liên quan đến nhiệm vụ của chúng. Interface nên được chia nhỏ theo chức năng cụ thể.

 

Ví dụ chưa tốt:

 

interface Machine {

  print(): void;

  scan(): void;

  fax(): void;

}

 

 

Một chiếc máy in đơn giản không cần scan hay fax, nhưng vẫn buộc phải implement nếu dùng interface này.

 

Giải pháp:

 

interface Printer {

  print(): void;

}


interface Scanner {

  scan(): void;

}
 
 

5. Dependency Inversion Principle – Ưu tiên phụ thuộc vào trừu tượng

 

Các module cấp cao không nên gắn chặt vào chi tiết cụ thể của module cấp thấp. Thay vào đó, cả hai nên phụ thuộc vào các abstraction (giao diện, lớp trừu tượng).

 

Ví dụ đúng cách:


 
interface IMessageService {

  sendMessage(msg: string): void;

}


class EmailService implements IMessageService {

  sendMessage(msg: string) {

    console.log("Send email:", msg);

  }

}


class Notification {

  constructor(private service: IMessageService) {}


  notify() {

    this.service.sendMessage("You have a new message");

  }

}

 

Bằng cách này, bạn có thể dễ dàng thay thế EmailService bằng dịch vụ khác như SMS hoặc push notification mà không cần chỉnh sửa Notification.

 

Kết luận

SOLID không phải là bộ quy tắc bắt buộc, nhưng là kim chỉ nam giúp bạn viết code rõ ràng, dễ phát triển và ít lỗi hơn. Khi áp dụng thường xuyên, bạn sẽ cảm nhận rõ hệ thống trở nên linh hoạt, dễ test và bền vững hơn trong dài hạn.

 HỖ TRỢ TRỰC TUYẾN