SOLID: các nguyên tắc, 5 quy tắc OOP và ứng dụng trong phát triển

Tác giả: IT Sectr Đã đăng: 2026-05-11 Thời gian đọc: 10 phút

SOLID — năm nguyên tắc lập trình hướng đối tượng được Robert C. Martin (Uncle Bob) đề ra vào đầu những năm 2000. Theo DigitalOcean, 2024, SOLID là viết tắt của Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation và Dependency Inversion. Các nguyên tắc này tạo nền tảng của Clean Architecture và được áp dụng trong phát triển Android (MVP, MVVM, Clean Architecture) và iOS (VIPER, TCA).

Ý chính

  • SOLID — từ viết tắt của năm nguyên tắc OOP: SRP, OCP, LSP, ISP, DIP, do Robert C. Martin đề ra để tạo mã linh hoạt và dễ bảo trì.
  • SRP (Single Responsibility) — mỗi lớp có một lý do để thay đổi, một trách nhiệm trên mỗi module.
  • OCP (Open-Closed) — các lớp mở cho việc mở rộng nhưng đóng cho việc sửa đổi, được thực hiện qua kế thừa và đa hình.
  • LSP (Liskov Substitution) — các đối tượng của lớp con phải có thể thay thế các đối tượng của lớp cơ sở mà không làm thay đổi tính đúng đắn của chương trình.
  • ISP (Interface Segregation) — khách hàng không nên phụ thuộc vào các giao diện mà họ không sử dụng, các giao diện phải hẹp và cụ thể.
  • DIP (Dependency Inversion) — các module cấp cao không phụ thuộc vào các module cấp thấp, cả hai đều phụ thuộc vào sự trừu tượng.

SOLID là gì? Tổng quan về năm nguyên tắc

SOLID — một từ viết tắt dễ nhớ đại diện cho năm nguyên tắc thiết kế hướng đối tượng. Thuật ngữ này được Robert C. Martin giới thiệu trong bài viết «Design Principles and Design Patterns» (2000) và sau đó được phổ biến trong cuốn sách «Agile Software Development: Principles, Patterns, and Practices» (2002). SOLID không phải là một framework hay thư viện — mà là một tập hợp các thực hành giúp mã ít phụ thuộc hơn, dễ kiểm tra hơn và dễ thay đổi hơn.

Theo Clean Coder Blog, 2014, mỗi nguyên tắc SOLID giải quyết một vấn đề thiết kế cụ thể: SRP chống lại các lớp God, OCP ngăn chặn các thay đổi dây chuyền, LSP bảo vệ khỏi kế thừa không chính xác, ISP tránh các giao diện lớn và DIP giảm sự phụ thuộc chặt chẽ. Cùng nhau, chúng tạo nên nền tảng của Clean Architecture, được sử dụng trong các dự án Android với MVP, MVVM và MVI.

SRP: Nguyên tắc Trách nhiệm Đơn lẻ

Single Responsibility Principle (SRP) — nguyên tắc trách nhiệm đơn lẻ. Công thức: «Một lớp chỉ nên có một lý do để thay đổi». Điều này có nghĩa là mỗi module hoặc lớp chịu trách nhiệm chính xác cho một chức năng hoặc một thực thể miền. Nếu một lớp quản lý cả người dùng và gửi email — nó có hai lý do để thay đổi, vi phạm SRP.

Theo Robert C. Martin, 2002, SRP là nguyên tắc quan trọng nhất và đồng thời bị vi phạm nhiều nhất. Trong phát triển di động, SRP thường bị vi phạm trong Activity/Fragment bằng cách kết hợp logic UI, điều hướng, mạng và logic nghiệp vụ. Giải pháp là tách mỗi lớp thành một lớp riêng: ViewModel cho logic UI, Repository cho dữ liệu, NavController cho điều hướng.

Ví dụ SRP: Phân tách UserManager

Xét lớp UserManager, lớp này tải hồ sơ, lưu cài đặt và gửi email. Đây là ba trách nhiệm riêng biệt, mỗi trách nhiệm cần được tách thành một lớp riêng: UserProfileRepository (tải), UserSettingsStorage (lưu) và EmailService (gửi). Mã khách hàng (ViewModel) sử dụng cả ba thông qua Dependency Injection, và mỗi lớp dễ dàng được kiểm tra riêng biệt và thay đổi mà không ảnh hưởng đến các lớp khác.

kotlin
// ❌ Vi phạm SRP: Activity biết về mạng, DB và UI
class ProfileActivity : AppCompatActivity() {
    fun loadProfile() {
        api.getUser() // Lời gọi mạng
        db.saveUser()    // Thao tác DB
        updateUI()         // Cập nhật UI
    }
}

// ✅ SRP được tuân thủ: các lớp đã được tách
class ProfileViewModel : ViewModel() {
    private val repo = UserRepository()
    fun loadProfile() { repo.getUser() }
}

Dấu hiệu vi phạm SRP: một lớp hơn 200 dòng, có các phương thức từ các miền khác nhau, thay đổi thường xuyên vì những lý do khác nhau. Đối với phát triển Android, quy tắc rất đơn giản: Activity chỉ xử lý vòng đời màn hình, ViewModel xử lý trạng thái UI, Repository xử lý các nguồn dữ liệu.

SRP và Kiến trúc Microservice

Nguyên tắc SRP không chỉ áp dụng cho các lớp mà còn cho kiến trúc ở cấp dịch vụ. Mỗi microservice xử lý một thực thể miền: UserService — chỉ người dùng, PaymentService — chỉ thanh toán, NotificationService — chỉ thông báo. Điều này cho phép mở rộng, triển khai và kiểm tra các dịch vụ một cách độc lập. Trong các ứng dụng di động, SRP ở cấp microservice thể hiện ở việc phân tách các client API theo miền.

OCP: Nguyên tắc Mở/Đóng

Open-Closed Principle (OCP) — các lớp nên được mở cho việc mở rộng (có thể thêm hành vi mới) và đóng cho việc sửa đổi (mã hiện tại không bị thay đổi). Điều này đạt được thông qua đa hình, các lớp trừu tượng và giao diện. Thay vì thêm if-else vào một phương thức hiện có, một triển khai giao diện mới được tạo ra.

Theo Clean Coder Blog, 2014, OCP hoạt động tốt nhất với mẫu Strategy. Ví dụ, nếu một ứng dụng hỗ trợ các phương thức thanh toán khác nhau (Google Pay, Apple Pay, PayPal), không cần thêm switch-case vào bộ xử lý thanh toán. Mỗi phương thức thanh toán triển khai một giao diện chung PaymentGateway, và một hệ thống thanh toán mới được thêm vào như một lớp mới mà không sửa đổi các lớp hiện có.

kotlin
// ✅ OCP: mở cho mở rộng, đóng cho sửa đổi
interface PaymentGateway {
    fun processPayment(amount: Double): Boolean
}

class GooglePayGateway : PaymentGateway {
    override fun processPayment(amount: Double) = true
}

// Hệ thống thanh toán mới — không thay đổi mã hiện có
class ApplePayGateway : PaymentGateway {
    override fun processPayment(amount: Double) = true
}

LSP: Nguyên tắc Thay thế Liskov

Liskov Substitution Principle (LSP) — nguyên tắc thay thế của Barbara Liskov. Nếu S là một kiểu con của T, thì các đối tượng kiểu T có thể được thay thế bằng các đối tượng kiểu S mà không làm thay đổi các thuộc tính của chương trình. Một cách chính thức: một hàm sử dụng lớp cơ sở phải hoạt động chính xác với bất kỳ lớp con nào của nó. Nếu một lớp con ném ngoại lệ ở nơi lớp cơ sở không ném — LSP đã bị vi phạm.

Theo Robert C. Martin, 2002, LSP là nguyên tắc SOLID khó hiểu nhất. Ví dụ vi phạm kinh điển là lớp Square kế thừa từ Rectangle. Nếu setWidth trên Square đặt cả chiều rộng và chiều cao, mã khách hàng mong đợi hành vi của Rectangle sẽ nhận được kết quả không mong đợi. Trong phát triển di động, LSP thường bị vi phạm khi kế thừa ViewModel — khi một ViewModel con thêm các phụ thuộc bắt buộc.

kotlin
// ❌ Vi phạm LSP: Square phá vỡ hành vi của Rectangle
open class Rectangle(open var width: Int, open var height: Int)

class Square(side: Int) : Rectangle(side, side) {
    override var width
        get() = super.width
        set(value) { super.setBoth(value, value) }
}

ISP: Nguyên tắc Phân tách Giao diện

Interface Segregation Principle (ISP) — khách hàng không nên phụ thuộc vào các giao diện mà họ không sử dụng. Thay vì một giao diện «béo» duy nhất, hãy tạo nhiều giao diện hẹp và chuyên biệt. Nếu một lớp triển khai một giao diện nhưng một số phương thức ném UnsupportedOperationException hoặc để trống — đó là dấu hiệu rõ ràng của việc vi phạm ISP.

Theo DigitalOcean, 2024, ISP đặc biệt quan trọng trong phát triển di động khi thiết kế ViewModel và Repository. Thay vì một giao diện UserRepository duy nhất với tất cả các phương thức CRUD, tốt hơn nên tạo QueryUserRepository (chỉ đọc) và CommandUserRepository (ghi). Khi đó, một client chỉ đọc (phần tử UI) chỉ phụ thuộc vào giao diện Query và không biết gì về các phương thức ghi.

kotlin
// ❌ Giao diện béo — khách hàng buộc phải triển khai các phương thức không cần thiết
interface UserOperations {
    fun getUser(id: String): User
    fun saveUser(user: User)
    fun deleteUser(id: String)
    fun exportUsers(): File
}

// ✅ ISP: các giao diện đã phân tách
interface UserReader { fun getUser(id: String): User }
interface UserWriter { fun saveUser(user: User) }
interface UserDeleter { fun deleteUser(id: String) }

DIP: Nguyên tắc Đảo ngược Phụ thuộc

Dependency Inversion Principle (DIP) — các module cấp cao không nên phụ thuộc vào các module cấp thấp. Cả hai nên phụ thuộc vào sự trừu tượng (các giao diện). Sự trừu tượng không nên phụ thuộc vào chi tiết — chi tiết nên phụ thuộc vào sự trừu tượng. Đây không phải là «Dependency Injection» (DI), mặc dù DI là một cách phổ biến để triển khai DIP.

Theo Robert C. Martin, 2019, DIP là nền tảng của Clean Architecture. ViewModel (cấp cao) không nên trực tiếp tạo một thể hiện RetrofitApi (chi tiết). Thay vào đó, ViewModel phụ thuộc vào một giao diện UserRepository, và triển khai cụ thể UserRepositoryImpl với Retrofit được truyền qua hàm tạo. Trong Android, DIP được triển khai thông qua Hilt/Dagger hoặc Koin: tất cả các phụ thuộc được cung cấp thông qua container DI.

kotlin
// ✅ DIP: Module phụ thuộc vào sự trừu tượng, không phải chi tiết
class UserRepositoryImpl(
    private val api: UserApi,   // Phụ thuộc vào giao diện
    private val db: UserDao     // Phụ thuộc vào giao diện
) : UserRepository {

    override suspend fun getUser(id: String): User {
        return api.fetchUser(id)
    }
}

// Hilt DI: các chi tiết được kết nối thông qua module DI
@Module
object NetworkModule {
    @Provides
    fun provideUserApi(retrofit: Retrofit): UserApi =
        retrofit.create(UserApi::class.java)
}

Áp dụng SOLID trong phát triển di động

SOLID trong phát triển di động được áp dụng ở tất cả các cấp: từ kiến trúc ứng dụng đến các lớp riêng lẻ. Trong các dự án Android, Clean Architecture chia mã thành ba lớp: domain (logic nghiệp vụ — độc lập với framework), data (kho lưu trữ, API, DB) và presentation (UI, ViewModel). Lớp domain sử dụng các nguyên tắc SOLID: các trường hợp sử dụng (SRP), giao diện kho lưu trữ (DIP), các lớp thực thể (OCP + LSP).

Theo Android Developers Guide, 2025, SRP trong Android thể hiện ở việc phân tách ViewModel, Repository và Mapper. OCP — khi thêm các nguồn dữ liệu mới thông qua giao diện DataSource. LSP — trong việc xử lý Result thống nhất trên các kho lưu trữ khác nhau. ISP — trong cách tiếp cận CQRS (phân tách kho lưu trữ đọc/ghi). DIP — thông qua Hilt/Koin để tiêm phụ thuộc.

Nguyên tắcVấn đề khi không cóGiải pháp trong dự án di động
SRPActivity hơn 1000 dòngViewModel + UseCase + Repository
OCPswitch-case theo loại thanh toánStrategy: giao diện PaymentGateway
LSPLỗi khi thay thế BaseViewModelKiểm tra hợp đồng lớp con
ISPUnsupportedOperationExceptionPhân tách Reader / Writer
DIPViewModel tạo Retrofit thủ côngContainer DI Hilt / Koin

Các lỗi thường gặp khi áp dụng SOLID

Các lỗi SOLID thường liên quan đến việc làm phức tạp hóa mã quá mức. Lỗi thứ nhất — tuân theo các nguyên tắc một cách máy móc mà không xem xét bối cảnh. Chia một lớp UserService thành 10 giao diện và 15 lớp chỉ vì một ISP «sạch» là thiết kế quá mức. SOLID là một công cụ, không phải mục tiêu. Lỗi thứ hai — nhầm lẫn SRP với «một phương thức = một trách nhiệm». Một lớp có thể có nhiều phương thức nếu tất cả chúng thuộc cùng một lĩnh vực trách nhiệm.

Theo Simple Thread, 2024, lỗi thứ ba — bỏ qua LSP khi kế thừa ViewModel trong Android. Nếu ViewModel cơ sở mong đợi LiveData nhưng ViewModel con sử dụng StateFlow — mã khách hàng đăng ký LiveData sẽ không nhận được cập nhật. Lỗi thứ tư — vi phạm DIP vì mục đích kiểm tra: RepositoryImpl trực tiếp tạo một thể hiện OkHttpClient, khiến việc kiểm tra đơn vị trở nên bất khả thi.

Quy tắc vàng: áp dụng SOLID khi nó giải quyết một vấn đề thực tế (thay đổi thường xuyên, khó kiểm tra, trùng lặp). Đối với các màn hình CRUD đơn giản, việc tuân thủ nghiêm ngặt cả năm nguyên tắc là quá mức. Đối với logic nghiệp vụ, tính toán tài chính và tương tác API, SOLID là điều cần thiết.

Mối liên hệ giữa SOLID và Clean Architecture

Clean Architecture (Robert C. Martin, 2012) — ứng dụng trực tiếp SOLID ở cấp độ lớp ứng dụng. SRP xác định ranh giới trường hợp sử dụng (mỗi trường hợp sử dụng — một lớp). OCP được triển khai thông qua các giao diện kho lưu trữ (Data Layer có thể thay đổi mà không sửa Domain). ISP cung cấp sự phân tách trường hợp sử dụng thành ranh giới đầu vào/đầu ra. DIP — hướng phụ thuộc vào bên trong lớp Domain. LSP đảm bảo rằng bất kỳ triển khai kho lưu trữ nào cũng có thể thay thế mà không làm hỏng các trường hợp sử dụng.

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

SOLID là gì một cách đơn giản?

SOLID — năm quy tắc viết mã dễ thay đổi, kiểm tra và hiểu. Mỗi chữ cái là một nguyên tắc: không viết lớp lớn (SRP), không thay đổi mã hiện tại — thêm mới (OCP), không phá vỡ hành vi của lớp con (LSP) và các nguyên tắc khác.

Nguyên tắc SOLID nào quan trọng nhất?

SRP (Single Responsibility) được coi là quan trọng nhất vì vi phạm của nó dẫn đến các lớp God — những lớp khổng lồ khó kiểm tra và thay đổi. Tuy nhiên, nếu không có DIP (Dependency Inversion), mã sẽ vẫn bị ràng buộc chặt chẽ, điều này cũng rất quan trọng.

SOLID có bắt buộc đối với phát triển di động không?

Không bắt buộc, nhưng rất được khuyến nghị cho các dự án thương mại có vòng đời dài. Đối với các ứng dụng đơn giản (một màn hình, không có logic nghiệp vụ), SOLID có thể là quá mức. Đối với các dự án có 50+ màn hình và 3+ nhà phát triển, SOLID là yêu cầu tối thiểu.

Chuyện gì xảy ra nếu không tuân thủ SOLID?

Hậu quả: các lớp trở nên «béo» (1000+ dòng), thay đổi ở một nơi làm hỏng ba nơi khác, không thể viết kiểm tra đơn vị, thêm tính năng mới mất hàng tuần thay vì hàng ngày. Theo thời gian, mã biến thành «Big Ball of Mud» — hỗn độn và dễ vỡ.

Làm thế nào để kiểm tra xem SOLID có được tuân thủ trong dự án không?

Dấu hiệu tuân thủ: mỗi lớp dưới 200 dòng, thay đổi một tính năng không ảnh hưởng đến 5+ tệp, có thể viết kiểm tra mà không cần giả lập 10 phụ thuộc, một nhà phát triển mới hiểu cấu trúc trong một ngày. Các công cụ như SonarQube và detekt giúp xác định các vi phạm SRP và DIP.

Tổng kết

  • SOLID — năm nguyên tắc OOP (SRP, OCP, LSP, ISP, DIP) để tạo mã linh hoạt và dễ bảo trì
  • SRP — mỗi thực thể chịu trách nhiệm một nhiệm vụ, giải quyết vấn đề lớp God
  • OCP — mở rộng thông qua đa hình, không phải sửa đổi mã hiện có
  • LSP — các lớp con không nên phá vỡ hành vi của lớp cơ sở
  • ISP — các giao diện hẹp thay vì «con dao Thụy Sĩ» vạn năng
  • DIP — phụ thuộc vào sự trừu tượng, tiêm qua Hilt/Koin trong Android
  • SOLID là cần thiết cho Clean Architecture và các dự án di động thương mại

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.

Thảo luận dự án

Đọc thêm