← SOLID Principles

Bài 7 · Nâng cao · 22 phút· Cập nhật 11/06/2026

Ứng dụng SOLID vào thực tế

Biên soạn bởi Nguyễn Anh Tuấn

Áp dụng cả 5 nguyên tắc SOLID vào một ví dụ checkout hoàn chỉnh: xem các nguyên tắc củng cố lẫn nhau ra sao, cân bằng để tránh over-engineering.

Năm nguyên tắc không tách rời - chúng cùng xuất hiện trong một thiết kế tốt. Lấy một hệ thống đặt hàng (checkout) làm ví dụ. Trước hết là các abstraction:

Các abstraction - ISP (nhỏ, chuyên biệt) + DIP (nghiệp vụ chỉ biết interface)

interface PaymentMethod { pay(amount: number): void; }  // OCP: them phuong thuc = them lop
interface OrderRepository { save(order: Order): void; } // DIP: nghiep vu biet interface
interface Notifier { notify(message: string): void; }  // ISP: interface nho, mot viec

Rồi các chi tiết cấp thấp - mỗi lớp tự lo phần của nó, và thay thế nhau được:

Implementation - SRP (mỗi lớp một việc) + LSP (đều thay thế được)

class CardPayment implements PaymentMethod {
  pay(amount: number) { /* tinh phi the */ }
}
class MoMoPayment implements PaymentMethod {
  pay(amount: number) { /* goi API MoMo */ }
}

class SqlOrderRepo implements OrderRepository {
  save(order: Order) { /* INSERT ... */ }
}
class EmailNotifier implements Notifier {
  notify(message: string) { /* gui email */ }
}

Lớp nghiệp vụ cấp cao chỉ ghép các bước, phụ thuộc vào abstraction và nhận implementation từ ngoài:

CheckoutService - SRP (chỉ điều phối) + DIP (tiêm abstraction)

class CheckoutService {
  constructor(
    private payment: PaymentMethod,   // DIP: tiem qua constructor
    private orders: OrderRepository,
    private notifier: Notifier,
  ) {}

  checkout(order: Order): void {
    this.payment.pay(order.total);    // khong biet Card hay MoMo (OCP + LSP)
    this.orders.save(order);
    this.notifier.notify("Da dat hang thanh cong");
  }
}

// Diem lap rap (composition root): chon implementation o MOT noi
const checkout = new CheckoutService(
  new MoMoPayment(),
  new SqlOrderRepo(),
  new EmailNotifier(),
);
  • Đổi MoMo → Card, SQL → Mongo, Email → SMS: chỉ sửa ở điểm lắp ráp.
  • CheckoutService test được bằng cách tiêm các bản giả (fake).
  • Thêm phương thức thanh toán mới không cần đụng CheckoutService.
SMỗi lớp một việc: CardPayment chỉ thanh toán, EmailNotifier chỉ báo tin, CheckoutService chỉ điều phối.
OThêm ZaloPayPayment = thêm một lớp; không sửa CheckoutService.
LMọi PaymentMethod giữ đúng hợp đồng pay() → thay thế nhau không phá luồng.
INotifier / PaymentMethod / OrderRepository là các interface nhỏ, không ép ai implement method thừa.
DCheckoutService phụ thuộc abstraction; implementation được tiêm từ composition root.

Củng cố lẫn nhau

Để ý chúng kéo theo nhau: tách trách nhiệm (SRP) làm interface nhỏ lại (ISP); interface + đa hình cho phép mở rộng (OCP)đảo chiều phụ thuộc (DIP); và tất cả chỉ vững nếu các biến thể thật sự thay thế được (LSP). SOLID là một tư duy, không phải 5 mẹo rời.
  • YAGNI: chưa cần thì chưa trừu tượng hoá - để thay đổi THỰC TẾ kéo abstraction ra đời.
  • Mỗi abstraction có giá: thêm lớp, thêm gián tiếp, khó lần theo luồng.
  • SOLID + Clean Code (vi mô) + Design Pattern (lời giải mẫu) bổ trợ nhau.
  • Mục tiêu cuối: phần mềm dễ thay đổi với chi phí thấp - không phải "đếm đủ 5 chữ".

La bàn, không phải bản đồ chi tiết

Nếu áp một nguyên tắc khiến code khó hiểu hơn cho đội của bạn, hãy dừng lại. Một interface chỉ có một implementation và sẽ mãi như vậy thường là dấu hiệu over-engineering. Hãy luyện trên dự án phụ trước khi đưa vào sản phẩm thật.

Chạy lại bộ tình huống - giờ bạn đã học cả 5 nguyên tắc, hãy xem mình nhận diện nhanh tới đâu:

🔍 Đoán xem: vi phạm nguyên tắc nào?

1/10
Lớp OrderProcessor vừa điều phối đặt hàng, vừa gọi cổng thanh toán, vừa tính phí ship và gửi email xác nhận.

Câu hỏi thường gặp

Chúng đan vào nhau quanh một mục tiêu chung - giảm phụ thuộc cứng. SRP tách trách nhiệm; ISP tách interface (SRP cho hợp đồng); OCP cho phép mở rộng nhờ đa hình; LSP bảo đảm các biến thể thật sự thay thế được; DIP đảo chiều phụ thuộc để nghiệp vụ độc lập với chi tiết. Làm tốt cái này thường kéo theo cái kia.

Phần lớn design pattern chính là SOLID được áp dụng cụ thể: Strategy (OCP), Factory/Dependency Injection (DIP), Adapter (ISP/DIP), Template Method (OCP/LSP)... Hiểu SOLID rồi học pattern sẽ thấy "à, hoá ra là vậy" thay vì học vẹt.

Có - chúng ở hai tầng. Clean Code lo mức "vi mô" (đặt tên, hàm nhỏ, định dạng, xử lý lỗi). SOLID lo mức "thiết kế" (tổ chức lớp & phụ thuộc). Code SOLID nhưng đặt tên tệ vẫn khó đọc; code đặt tên đẹp nhưng God class vẫn khó bảo trì. Cần cả hai.

Dấu hiệu: interface chỉ có một implementation và sẽ mãi như vậy; phải mở 5 file mới hiểu một luồng đơn giản; abstraction được tạo "phòng xa" cho thứ chưa bao giờ tới. Quy tắc YAGNI: chưa cần thì chưa trừu tượng hoá. Hãy để thay đổi THỰC TẾ kéo abstraction ra đời.

Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.

Tick những điều em tự tin làm được sau khi học bài này. 0/6

Trả lời vài câu để chắc rằng em đã nắm bài.

Câu 1/4 Điểm: 0

Phần lớn các design pattern thực ra là gì?

Bài tập về nhà

  1. 1

    Bản đồ nguyên tắc

    Lấy ví dụ CheckoutService ở Bước 1-2. Với mỗi chữ S-O-L-I-D, chỉ ra nó thể hiện ở dòng/phần nào.

    ✅ Hoàn thành khi: Gắn đúng cả 5 nguyên tắc vào các phần cụ thể của code.

  2. 2

    Thêm phương thức thanh toán

    Thêm ZaloPayPayment vào ví dụ mà KHÔNG sửa CheckoutService - chứng minh OCP + DIP đang hoạt động.

    ✅ Hoàn thành khi: Chỉ thêm một lớp mới; CheckoutService giữ nguyên; lắp ở composition root.

  3. 3

    Áp dụng vào module của mèo con

    Chọn một module nhỏ trong dự án và refactor để thể hiện ít nhất 3 trong 5 nguyên tắc.

    ✅ Hoàn thành khi: Nêu rõ trước/sau; chỉ ra 3 nguyên tắc đã áp dụng và lợi ích thu được.

  4. 4

    Vạch ranh giới YAGNI

    Trong chính refactor trên, nêu một chỗ em CỐ TÌNH không trừu tượng hoá và giải thích vì sao (tránh over-engineering).

    ✅ Hoàn thành khi: Quyết định có lý do; cân bằng được giữa SOLID và KISS/YAGNI.