Bài 2 · Vận dụng · 20 phút· Cập nhật 11/06/2026
S - Single Responsibility
Biên soạn bởi Nguyễn Anh Tuấn
Single Responsibility Principle (SRP): nguyên tắc đơn nhiệm, mỗi lớp chỉ chịu trách nhiệm về một và chỉ một nhiệm vụ cụ thể, dễ test và bảo trì.
Single Responsibility Principle (SRP): mỗi lớp chỉ nên chịu trách nhiệm về một việc duy nhất - hay nói cách khác, chỉ có một lý do để thay đổi. Hãy xem một lớp vi phạm:
❌ Vi phạm SRP - OrderProcessor làm hết
class OrderProcessor {
checkout(order: Order) {
// (1) Xu ly thanh toan
const ok = chargeCard(order.card, order.total);
if (!ok) throw new Error("Thanh toan that bai");
// (2) Xu ly van chuyen
const label = createShippingLabel(order.address);
bookCourier(label);
// (3) Gui email xac nhan
sendEmail(order.email, "Don hang da xac nhan");
}
} - ▸Một lớp gánh BA trách nhiệm: thanh toán, vận chuyển, thông báo.
- ▸Ba "tác nhân" khác nhau (cổng thanh toán, hãng ship, hệ thống mail) đều có thể buộc sửa lớp này.
- ▸Đổi nhà cung cấp email cũng phải mở đúng lớp xử lý thanh toán - rủi ro gãy chéo.
Tách mỗi trách nhiệm thành một lớp riêng, rồi để một lớp điều phối ghép chúng lại:
✅ Tuân thủ SRP - tách trách nhiệm
class PaymentProcessor {
pay(order: Order): void {
const ok = chargeCard(order.card, order.total);
if (!ok) throw new Error("Thanh toan that bai");
}
}
class ShippingProcessor {
ship(order: Order): void {
const label = createShippingLabel(order.address);
bookCourier(label);
}
}
class OrderNotifier {
confirm(order: Order): void {
sendEmail(order.email, "Don hang da xac nhan");
}
}
// Lop dieu phoi: chi GHEP cac buoc, khong tu lam chi tiet
class OrderProcessor {
constructor(
private payment: PaymentProcessor,
private shipping: ShippingProcessor,
private notifier: OrderNotifier,
) {}
checkout(order: Order): void {
this.payment.pay(order);
this.shipping.ship(order);
this.notifier.confirm(order);
}
} - ▸Mỗi lớp giờ chỉ có MỘT lý do để thay đổi.
- ▸Đổi cổng thanh toán → chỉ sửa PaymentProcessor; phần còn lại không đụng tới.
- ▸OrderProcessor trở thành "nhạc trưởng" - chỉ điều phối, không ôm chi tiết.
Tách trách nhiệm mở đường cho DIP
Lớp một trách nhiệm thì test gọn - không phải dựng cả luồng đặt hàng để kiểm thanh toán:
payment.test.ts - test độc lập
import { describe, it, expect, vi } from "vitest";
import { chargeCard } from "./payment-gateway";
vi.mock("./payment-gateway"); // mock module truoc, vi.mocked moi co tac dung
describe("PaymentProcessor", () => {
it("nem loi khi the bi tu choi", () => {
vi.mocked(chargeCard).mockReturnValue(false);
const order = { id: 1, total: 499000 };
expect(() => new PaymentProcessor().pay(order))
.toThrow("Thanh toan that bai");
});
}); - ▸Test PaymentProcessor không cần email hay vận chuyển.
- ▸Ít phụ thuộc → ít mock → test nhanh, rõ ý.
- ▸Lỗi ở đâu lộ ra ở đó: test đỏ chỉ thẳng vào trách nhiệm bị hỏng.
- ▸Dấu hiệu vi phạm: tên lớp có "And" hoặc "Manager/Handler" mơ hồ (Processor kèm phạm vi hẹp như PaymentProcessor thì ổn); method dài; nhiều import không liên quan.
- ▸Hỏi "lớp này thay đổi vì ai/vì điều gì?" - nếu trả lời được nhiều thứ, hãy tách.
- ▸Đừng tách tới mức vô nghĩa: SRP là về LÝ DO thay đổi, không phải "mỗi lớp một dòng".
Một lý do để thay đổi
Câu hỏi thường gặp
Uncle Bob diễn đạt lại SRP là: "một lớp chỉ nên có MỘT lý do để thay đổi", và mỗi lý do thường gắn với một TÁC NHÂN (actor) - một nhóm người/bộ phận yêu cầu thay đổi. Nếu phòng Kế toán và phòng Vận hành đều có thể khiến bạn phải sửa cùng một lớp, lớp đó đang gánh hai trách nhiệm.
Không. SRP nói về một LÝ DO ĐỂ THAY ĐỔI, không phải một method. Một lớp có thể có nhiều method miễn chúng cùng phục vụ một trách nhiệm gắn kết (cohesive). Tách quá nhỏ (mỗi lớp một method vô nghĩa) lại là một thái cực sai khác.
Số file tăng là đánh đổi có thật. Nhưng mỗi file ngắn, tên rõ, dễ tìm và dễ test hơn một "God class" 800 dòng. Đặt tên & tổ chức thư mục tốt sẽ bù lại. Với module rất nhỏ và ổn định, đừng tách quá tay.
Cân bằng để không lạm dụng: bài Áp dụng SOLID tổng hợp →Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
Single Responsibility Principle phát biểu rằng một lớp nên…
- 1
Đếm "lý do để thay đổi"
Lấy lớp
OrderProcessor"bẩn" ở Bước 1. Liệt kê từng lý do khiến nó phải sửa, và gắn mỗi lý do với một tác nhân (Kế toán, Vận hành, Marketing…).✅ Hoàn thành khi: Liệt kê ≥3 lý do, mỗi lý do gắn đúng một tác nhân khác nhau.
- 2
Tách trách nhiệm
Refactor một lớp ôm đồm trong code của mèo con (hoặc lớp
Report: truy vấn + tính + dựng HTML) thành các lớp nhỏ theo SRP.✅ Hoàn thành khi: Mỗi lớp mới có một trách nhiệm rõ; lớp điều phối chỉ ghép chúng lại, không tự làm hết.
- 3
Test từng phần
Viết unit test cho MỘT lớp đã tách (vd
PaymentProcessor) mà không cần dựng các phần khác.✅ Hoàn thành khi: Test chạy độc lập, không phụ thuộc email/vận chuyển; cover ít nhất một ca thành công + một ca lỗi.
- 4
Đừng tách quá tay
Tìm một ví dụ tách nhỏ tới mức gây rối (vd mỗi getter một lớp). Lập luận vì sao nó vi phạm tinh thần thay vì phục vụ SRP.
✅ Hoàn thành khi: Nêu được rằng SRP là về "lý do thay đổi", không phải "càng nhỏ càng tốt".