Khi bắt đầu hành trình trở thành một lập trình viên chuyên nghiệp, một trong những khái niệm nền tảng bạn sẽ gặp là SOLID. Đây là bộ 5 nguyên tắc thiết kế hướng đối tượng giúp chúng ta viết code dễ đọc, dễ bảo trì và dễ mở rộng. Trong bài viết này, chúng ta sẽ mổ xẻ chữ cái đầu tiên: S - Single Responsibility Principle (SRP).
Nguyên tắc này được Robert C. Martin (Uncle Bob) phát biểu một cách ngắn gọn:
“A class should have only one reason to change.”
Tức là, một lớp chỉ nên chịu trách nhiệm về một khía cạnh duy nhất của chức năng. Nếu một lớp có nhiều hơn một lý do để thay đổi, nó đang vi phạm nguyên tắc này.
Tại Sao Điều Này Quan Trọng?
Khi một lớp “ôm đồm” quá nhiều việc, nó sẽ trở thành một mớ hỗn độn. Một thay đổi nhỏ ở một chức năng có thể vô tình làm hỏng một chức năng khác không liên quan, dẫn đến những bug khó lường. Code cũng trở nên khó tái sử dụng hơn.
Ví Dụ Chính Xác: Quản Lý Người Dùng
Hãy xem một ví dụ kinh điển để thấy rõ sự khác biệt.
Ví dụ Xấu: Vi phạm SRP
Giả sử chúng ta có một lớp `UserProcessor` làm hai việc: kiểm tra dữ liệu người dùng (validation) và lưu người dùng vào cơ sở dữ liệu (persistence).
// VI PHẠM: Lớp này có hai lý do để thay đổi
class UserProcessor_Bad {
// Trách nhiệm 1: Validate dữ liệu
public boolean validateUser(User user) {
if (user.getName() == null || user.getName().trim().isEmpty()) {
System.err.println("User name cannot be empty.");
return false;
}
if (user.getEmail() == null || !user.getEmail().contains("@")) {
System.err.println("Invalid user email.");
return false;
}
return true;
}
// Trách nhiệm 2: Lưu vào database
public void saveUserToDatabase(User user) {
// Giả lập logic lưu vào cơ sở dữ liệu
System.out.println("Saving user " + user.getName() + " to the database...");
}
// Một phương thức tiện ích kết hợp cả hai
public void processUser(User user) {
if (validateUser(user)) {
saveUserToDatabase(user);
}
}
}
// Lớp User đơn giản
class User {
private String name;
private String email;
// constructor, getters, setters
public User(String name, String email) { this.name = name; this.email = email; }
public String getName() { return name; }
public String getEmail() { return email; }
}
Lớp `UserProcessor_Bad` này có hai lý do để thay đổi:
- Khi quy tắc validation thay đổi (ví dụ: yêu cầu tên phải có ít nhất 3 ký tự).
- Khi cách thức lưu trữ thay đổi (ví dụ: chuyển từ ghi file sang dùng MySQL, hoặc thêm caching).
Việc gộp chung chúng làm cho lớp trở nên cứng nhắc và khó bảo trì.
Ví dụ Tốt: Tuân Thủ SRP
Bây giờ, chúng ta sẽ tách các trách nhiệm ra thành các lớp riêng biệt.
// TỐT: Chỉ có một trách nhiệm - Validation
class UserValidator {
public boolean validate(User user) {
if (user.getName() == null || user.getName().trim().isEmpty()) {
System.err.println("User name cannot be empty.");
return false;
}
if (user.getEmail() == null || !user.getEmail().contains("@")) {
System.err.println("Invalid user email.");
return false;
}
return true;
}
}
// TỐT: Chỉ có một trách nhiệm - Persistence (Lưu trữ)
// Thường được gọi là "Repository"
class UserRepository {
public void save(User user) {
// Giả lập logic lưu vào cơ sở dữ liệu
System.out.println("Saving user " + user.getName() + " to the database...");
}
}
// TỐT: Lớp service này điều phối các lớp khác, không tự mình thực hiện logic
class UserService {
private final UserValidator validator;
private final UserRepository repository;
public UserService(UserValidator validator, UserRepository repository) {
this.validator = validator;
this.repository = repository;
}
public void registerUser(User user) {
// Ủy quyền việc validation cho validator
if (validator.validate(user)) {
// Ủy quyền việc lưu trữ cho repository
repository.save(user);
System.out.println("User registration successful.");
} else {
System.err.println("User registration failed due to validation errors.");
}
}
}
// Lớp User (giữ nguyên)
// class User { ... }
Với thiết kế mới này:
UserValidatorchỉ có một lý do để thay đổi: khi luật lệ validation thay đổi.UserRepositorychỉ có một lý do để thay đổi: khi công nghệ lưu trữ thay đổi.UserServiceđóng vai trò điều phối viên. Trách nhiệm của nó là sắp xếp các bước, không phải thực thi logic nghiệp vụ chi tiết.
Code của chúng ta giờ đây rõ ràng, linh hoạt và dễ dàng cho việc kiểm thử (unit test) từng thành phần riêng biệt.
Kết Luận
Nguyên tắc Đơn Trách Nhiệm là bước đầu tiên và quan trọng nhất để xây dựng một kiến trúc phần mềm vững chắc. Bằng cách đảm bảo mỗi lớp chỉ làm một việc duy nhất, bạn đang tạo ra một nền móng vững chắc cho code của mình, giúp nó không bị “mục nát” theo thời gian.