SRP (Single Responsibility Principle) — nguyên tắc đầu tiên của SOLID, quy định: mỗi lớp hoặc module phải có đúng một lý do để thay đổi. Nguyên tắc này được Robert Martin xây dựng trong cuốn sách Clean Architecture (2017) và trở thành nền tảng của thiết kế module. Theo cuốn sách này, áp dụng SRP trực tiếp giảm sự kết dính thành phần và loại bỏ các thay đổi dây chuyền khi sửa đổi chức năng.
Ý chính
SRP (Single Responsibility Principle) là nguyên tắc trách nhiệm duy nhất, phát biểu rằng: mỗi lớp hoặc module phải có đúng một lý do để thay đổi. Điều này không có nghĩa là một lớp chỉ nên thực hiện đúng một thao tác. Nó đề cập đến một nhóm các hành động liên quan được gắn kết bởi một trách nhiệm duy nhất đối với một tác nhân.
Robert Martin đã định nghĩa lại SRP theo khía cạnh tác nhân: một lớp chỉ nên thay đổi theo yêu cầu của một bên liên quan hoặc một nhóm người. Nếu hai tác nhân khác nhau yêu cầu thay đổi trên cùng một lớp, trách nhiệm đã bị phân chia không đúng.
Ví dụ, một lớp Employee vừa tính lương (yêu cầu của phòng kế toán) vừa tạo báo cáo (yêu cầu của ban quản lý) đã vi phạm SRP. Thay đổi quy tắc tính toán có thể ảnh hưởng đến việc tạo báo cáo và ngược lại.
Một module phải có một và chỉ một lý do để thay đổi. Lý do thay đổi được xác định bởi một tác nhân — người hoặc hệ thống khởi tạo yêu cầu. Nếu yêu cầu từ các tác nhân khác nhau dẫn đến thay đổi trong cùng một module, thì module đó vi phạm SRP.
Khái niệm tác nhân biến SRP thành một công cụ phân tích kiến trúc thực tế, không phải là một khuyến nghị trừu tượng. Khi thiết kế hệ thống, chỉ cần hỏi: “Ai sẽ yêu cầu thay đổi mã này?” — nếu câu trả lời có nhiều hơn một bên liên quan, trách nhiệm cần được phân chia.
Trách nhiệm duy nhất được thực hiện bằng cách nhóm các phương thức thay đổi vì một lý do. Lớp trở thành “điểm tập hợp” logic liên quan thay vì một “con dao gây Thụy Sĩ” cho mọi trường hợp. Điều này đơn giản hóa việc hiểu mã: nhà phát triển nhìn vào lớp và hiểu ngay mục đích của nó.
Cơ chế hoạt động của SRP dựa trên quy tắc một trục thay đổi. Nếu một chức năng có thể thay đổi vì những lý do độc lập, nó cần được tách ra thành các lớp riêng biệt. Kết nối giữa các lớp này được xây dựng thông qua composition hoặc ủy quyền.
Vi phạm SRP thể hiện ở các “God Objects” — những lớp có hàng chục phương thức làm việc với dữ liệu khác nhau. Lớp như vậy rất khó kiểm thử — kiểm thử một phương thức đòi hỏi phải thiết lập môi trường cho tất cả các phương thức khác. Thay đổi một trách nhiệm có thể làm hỏng trách nhiệm khác, khiến mã trở nên dễ vỡ.
Trong thực tế, SRP giúp các nhà phát triển trả lời câu hỏi “Mã này ở đâu?” Nếu mỗi trách nhiệm được tách riêng trong lớp của nó, việc tìm tập tin chính xác chỉ mất vài giây. Trong dự án Android với kiến trúc MVVM, UserViewModel chỉ chịu trách nhiệm về trạng thái màn hình người dùng, còn UserRepository chịu trách nhiệm lấy dữ liệu. Nhà phát triển tìm logic cache sẽ vào UserCacheRepository, không phải ViewModel. Tổ chức mã như vậy tăng tốc độ hòa nhập cho thành viên mới và giảm số lượng lỗi khi tái cấu trúc.
Phát triển di động đòi hỏi tính module hóa cao của mã. Fragment Android hoặc ViewController iOS thường trở thành “nam châm” hút logic: xử lý chạm, gọi API, phân tích phản hồi, cập nhật UI — tất cả trong một lớp. SRP yêu cầu tách các trách nhiệm này.
Trong kiến trúc Android, SRP được tích hợp trong các khuyến nghị của Google về Jetpack: ViewModel chịu trách nhiệm trạng thái màn hình, Repository chịu trách nhiệm dữ liệu, UseCase chịu trách nhiệm logic nghiệp vụ. Trong phát triển iOS, các pattern MVVM và Coordinator tuân theo cùng logic đó.
Tuân thủ SRP trong các dự án di động mang lại lợi ích có thể đo lường: giảm 40-60% kích thước lớp, giảm thời gian review mã và giảm lỗi hồi quy khi thêm chức năng mới. Các module biệt lập dễ dàng được bao phủ bởi kiểm thử đơn vị và tái sử dụng trên các màn hình khác.
Kiểm thử đơn vị các lớp tuân thủ SRP cần ít đối tượng giả và ít cấu hình hơn. Nếu một lớp có một trách nhiệm duy nhất, các phụ thuộc của nó bị giới hạn. Bài kiểm thử xác minh một hành vi, không phải sự kết hợp của nhiều kịch bản không liên quan.
Theo báo cáo của Google Testing Blog (2023), các lớp có trách nhiệm duy nhất cho thấy độ bao phủ kiểm thử cao hơn 35% so với các lớp tổng hợp. Các nhà phát triển sẵn sàng viết kiểm thử cho các module nhỏ, dễ hiểu hơn.
Hãy xem xét một lớp Android điển hình vi phạm SRP — nó tải dữ liệu, phân tích phản hồi và cập nhật UI. Sau khi tái cấu trúc, mỗi trách nhiệm được tách thành component riêng.
// Vi phạm SRP: một lớp làm mọi thứ
class BadUserProfileActivity {
fun loadUser(userId: Int) {
// Yêu cầu HTTP
// Phân tích JSON
// Cập nhật UI
// Lưu vào DB
}
}
// Sau khi áp dụng SRP
class UserRepository {
fun getUser(userId: Int): User
}
class UserViewModel {
private val repo: UserRepository
fun loadUser(userId: Int) { }
}
class UserProfileFragment {
fun render(user: User) { }
}
Một ví dụ tương tự trong iOS Swift với việc tách lớp mạng và hiển thị:
// Vi phạm SRP: ViewController quản lý dữ liệu và UI
class BadProfileViewController: UIViewController {
func viewDidLoad() {
// Yêu cầu URLSession
// Giải mã JSON
// Cập nhật label
}
}
// Sau khi áp dụng SRP
protocol UserServiceProtocol {
func fetchUser(id: Int) async throws -> User
}
class ProfileViewModel {
private let service: UserServiceProtocol
func loadProfile(id: Int) { }
}
class ProfileViewController: UIViewController {
func display(user: User) { }
}
Tái cấu trúc SRP không làm phức tạp kiến trúc — nó phân phối lại trách nhiệm. Lượng mã thậm chí có thể giảm bằng cách loại bỏ trùng lặp. Mỗi lớp mới có mục đích rõ ràng và có thể được phát triển độc lập.
Composition giúp duy trì SRP ở những nơi kế thừa tạo ra sự kết dính không cần thiết. Thay vì một siêu lớp với hàng chục phương thức, lớp con nhận được một tập các đối tượng chuyên biệt thông qua hàm tạo. Mỗi đối tượng chịu trách nhiệm về chức năng riêng của nó.
Trong phát triển Android, pattern Decorator cho phép thêm trách nhiệm mà không sửa đổi lớp gốc. Trong iOS, một chuỗi Middleware trong lớp mạng tách riêng logging, caching và xác thực thành các module riêng biệt.
Vi phạm phổ biến nhất là “God Class”: một lớp quản lý cơ sở dữ liệu, gửi thông báo, tạo báo cáo và xử lý đầu vào người dùng. Lớp như vậy trở thành nút cổ chai của dự án: bất kỳ thay đổi nào cũng cần kiểm thử hồi quy đầy đủ.
Trong phát triển di động, việc trộn lẫn logic nghiệp vụ và logic UI trong Activity, Fragment hoặc ViewController dẫn đến vi phạm SRP. Khi một onClickListener đồng thời xác thực dữ liệu, gọi API và cập nhật khả năng hiển thị nút — đó là sự vi phạm trực tiếp nguyên tắc trách nhiệm duy nhất.
Hậu quả của việc vi phạm SRP bao gồm: khó khăn trong phát triển song song (xung đột trong cùng một tập tin), kiểm thử đơn vị khó khăn, chi phí thay đổi cao và giảm khả năng đọc mã. Các dự án có vi phạm SRP hệ thống cần gấp 2-3 lần thời gian để thêm chức năng mới.
Có thể xác định vi phạm SRP qua các dấu hiệu gián tiếp: lớp vượt quá 200 dòng, import module từ các tầng ứng dụng khác nhau (UI + network + database), có hơn 5 phương thức công khai về các chủ đề khác nhau. Chỉ số cohesion là một chỉ báo thống kê: cohesion thấp giữa các phương thức trong một lớp cho thấy vi phạm SRP.
Để phát hiện vi phạm SRP, hãy sử dụng công cụ phân tích tĩnh: cho Android — Detekt với quy tắc TooManyFunctions, cho iOS — SwiftLint với quy tắc file_length. Các công cụ này làm nổi bật các lớp vượt quá ngưỡng kích thước và độ phức tạp.
Tái cấu trúc các lớp vi phạm SRP được thực hiện thông qua Extract Class hoặc Extract Delegate: một nhóm các phương thức liên quan được tách thành một lớp riêng, và lớp gốc ủy quyền lời gọi cho chúng. Áp dụng dần các bước tái cấu trúc này biến “God Class” thành một tập hợp các module kết dính lỏng lẻo, mỗi module có một trách nhiệm duy nhất. Cách tiếp cận này cho phép cải thiện kiến trúc mà không dừng phát triển — tái cấu trúc được thực hiện lặp lại, một module một lần.
Câu hỏi thường gặp
Không. SRP không nói về số lượng phương thức, mà nói về số lượng lý do để thay đổi. Một lớp có thể có hàng chục phương thức nếu tất cả đều phục vụ một trách nhiệm duy nhất đối với một tác nhân. Một phương thức là thái cực ngược lại, dẫn đến phân mảnh mã quá mức.
Đó là cùng một nguyên tắc. Single Responsibility Principle được dịch là “trách nhiệm duy nhất” và “nghĩa vụ duy nhất”. Thuật ngữ “trách nhiệm” phản ánh đúng bản chất hơn: nói về trách nhiệm đối với một tác nhân, không phải một chức năng kỹ thuật.
Repository là kết quả trực tiếp của việc áp dụng SRP vào tầng dữ liệu. Thay vì rải rác logic truy cập dữ liệu trong ViewModel hoặc UseCase, Repository đảm nhận một trách nhiệm duy nhất: cung cấp dữ liệu với tính trừu tượng nguồn. Đây là cách triển khai SRP kinh điển trong kiến trúc di động.
Có, SRP không cấm các phụ thuộc. Một lớp với một trách nhiệm duy nhất có thể ủy quyền một phần công việc cho các lớp khác thông qua composition. Điều quan trọng là các tác vụ được ủy quyền này phải là một phần của cùng trách nhiệm, không phải một lý do thay đổi độc lập.
Đặt câu hỏi: “Những tác nhân nào có thể yêu cầu thay đổi lớp này?” Nếu câu trả lời có nhiều hơn một tác nhân, SRP đã bị vi phạm. Thêm vào đó: hãy thử mô tả mục đích của lớp trong một câu không có từ “và”. Nếu không thể, lớp đang làm quá nhiều việc.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm