SOLID:面向对象编程的5个原则及其在开发中的应用

作者: IT Sectr 发布日期: 2026-05-11 阅读时间: 10 分钟

SOLID — 由Robert C. Martin(Uncle Bob)在21世纪初提出的五项面向对象编程原则。根据 DigitalOcean, 2024SOLID 代表单一职责、开闭原则、里氏替换、接口隔离和依赖反转。这些原则构成了Clean Architecture的基础,并应用于Android(MVP、MVVM、Clean Architecture)和iOS(VIPER、TCA)开发中。

要点

  • SOLID — 五个OOP原则的首字母缩写:SRP、OCP、LSP、ISP、DIP,由Robert C. Martin提出,用于创建灵活且可维护的代码。
  • SRP(单一职责) — 每个类只有一个改变的理由,每个模块只负责一件事。
  • OCP(开闭原则) — 类应对扩展开放,对修改关闭,通过继承和多态实现。
  • LSP(里氏替换) — 子类对象应能替换基类对象而不改变程序的正确性。
  • ISP(接口隔离) — 客户端不应依赖它们不使用的接口,接口应小而专。
  • DIP(依赖反转) — 高层模块不应依赖低层模块,两者都应依赖抽象。

什么是SOLID?五项原则概述

SOLID — 一个助记缩略词,表示五项面向对象设计原则。该术语由Robert C. Martin在文章“Design Principles and Design Patterns”(2000)中引入,后来在“Agile Software Development: Principles, Patterns, and Practices”(2002)一书中普及。SOLID 不是框架或库 — 它是一套实践,使代码更少耦合、更易测试、更易修改。

根据 Clean Coder Blog, 2014,每个SOLID原则解决特定的设计问题:SRP对抗上帝类,OCP应对级联变更,LSP处理不正确的继承,ISP解决臃肿的接口,DIP消除紧耦合。它们共同构成了Clean Architecture的基础,用于采用MVP、MVVM和MVI架构的Android项目。

SRP:单一职责原则

单一职责原则(SRP) — 公式:“一个类应该只有一个改变的理由。”这意味着每个模块或类恰好负责一个功能或一个领域实体。如果一个类同时管理用户和发送电子邮件 — 它有两个改变的理由,这就违反了SRP。

根据 Robert C. Martin, 2002SRP 是最重要也是最常被违反的原则。在移动开发中,SRP经常在Activity/Fragment中被违反,将UI逻辑、导航、网络工作和业务逻辑混合在一起。解决方案 — 将每一层分离到单独的类中:ViewModel负责UI逻辑,Repository负责数据,NavController负责导航。

SRP示例:拆分UserManager

考虑 UserManager 类,它加载个人资料、保存设置和发送电子邮件。这是三个不同的职责,每个都应分离到单独的类:UserProfileRepository(加载)、UserSettingsStorage(保存)和EmailService(发送)。客户端代码(ViewModel)通过依赖注入使用所有三个类,每个类都可以轻松隔离测试并独立修改而不影响其他类。

kotlin
// ❌ 违反SRP:Activity知道网络、数据库和UI
class ProfileActivity : AppCompatActivity() {
    fun loadProfile() {
        api.getUser() // 网络调用
        db.saveUser()    // 数据库操作
        updateUI()         // UI更新
    }
}

// ✅ 遵守SRP:各层已分离
class ProfileViewModel : ViewModel() {
    private val repo = UserRepository()
    fun loadProfile() { repo.getUser() }
}

违反SRP的迹象:类包含超过200行,有来自不同领域的方法,经常因不同原因而改变。对于Android开发,规则很简单:Activity只负责屏幕的生命周期,ViewModel负责UI状态,Repository负责数据源。

SRP与微服务架构

SRP 原则不仅适用于类,也适用于服务级别的架构。每个微服务负责一个领域实体:UserService — 仅用户,PaymentService — 仅支付,NotificationService — 仅通知。这允许服务独立扩展、部署和测试。在移动应用中,微服务级别的SRP体现在API客户端按领域划分上。

OCP:开闭原则

开闭原则(OCP) — 类应对扩展开放(可以添加新行为),对修改关闭(现有代码不变)。通过多态、抽象类和接口实现。与其在现有方法中添加if-else,不如创建接口的新实现。

根据 Clean Coder Blog, 2014OCP 与策略模式结合时最为有效。例如,如果应用程序支持不同的支付方式(Google Pay、Apple Pay、PayPal),不需要在支付处理器中添加switch-case。每种支付方式实现共同的PaymentGateway接口,新的支付系统作为新类添加,无需修改现有代码。

kotlin
// ✅ OCP:对扩展开放,对修改关闭
interface PaymentGateway {
    fun processPayment(amount: Double): Boolean
}

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

// 新的支付系统 — 无需修改现有代码
class ApplePayGateway : PaymentGateway {
    override fun processPayment(amount: Double) = true
}

LSP:里氏替换原则

里氏替换原则(LSP) — 如果S是T的子类型,那么T的对象可以替换为S的对象,而不改变程序的属性。正式来说:使用基类的函数应能与其任何子类正确工作。如果子类在基类不抛出异常的地方抛出异常 — LSP被违反。

根据 Robert C. Martin, 2002LSP 是最难理解的SOLID原则。经典违反示例 — Square(正方形)类继承Rectangle(矩形)。如果Square的setWidth同时设置了宽度和高度,期望Rectangle行为的客户端代码将得到意外结果。在移动开发中,LSP常在ViewModel继承时被违反,当子ViewModel添加了强制依赖项。

kotlin
// ❌ 违反LSP:Square破坏了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:接口隔离原则

接口隔离原则(ISP) — 客户端不应依赖它们不使用的接口。不要创建一个“臃肿”的接口,而是创建多个小而专的接口。如果一个类实现了一个接口,但部分方法抛出UnsupportedOperationException或为空 — 这是违反ISP的明显迹象。

根据 DigitalOcean, 2024ISP 在移动开发中设计ViewModel和Repository时尤为重要。不要创建一个包含所有CRUD方法的UserRepository接口,最好创建QueryUserRepository(只读)和CommandUserRepository(写入)。这样,读取客户端(UI元素)只依赖Query接口,不知道写入方法。

kotlin
// ❌ 臃肿的接口 — 客户端被迫实现不需要的方法
interface UserOperations {
    fun getUser(id: String): User
    fun saveUser(user: User)
    fun deleteUser(id: String)
    fun exportUsers(): File
}

// ✅ ISP:分离的接口
interface UserReader { fun getUser(id: String): User }
interface UserWriter { fun saveUser(user: User) }
interface UserDeleter { fun deleteUser(id: String) }

DIP:依赖反转原则

依赖反转原则(DIP) — 高层模块不应依赖低层模块。两者都应依赖抽象(接口)。抽象不应依赖细节 — 细节应依赖抽象。这与“依赖注入”(DI)不同,尽管DI是实现DIP的常见方式。

根据 Robert C. Martin, 2019DIP 是Clean Architecture的基础。ViewModel(高层)不应直接创建RetrofitApi(细节)的实例。相反,ViewModel依赖UserRepository接口,具体的UserRepositoryImpl实现(使用Retrofit)通过构造函数传入。在Android中,DIP通过Hilt/Dagger或Koin实现:所有依赖项通过DI容器提供。

kotlin
// ✅ DIP:模块依赖抽象而非细节
class UserRepositoryImpl(
    private val api: UserApi,   // 依赖接口
    private val db: UserDao     // 依赖接口
) : UserRepository {

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

// Hilt DI:细节通过DI模块连接
@Module
object NetworkModule {
    @Provides
    fun provideUserApi(retrofit: Retrofit): UserApi =
        retrofit.create(UserApi::class.java)
}

SOLID在移动开发中的应用

SOLID在移动开发中 应用于所有级别:从应用程序架构到单个类。在Android项目中,Clean Architecture将代码分为三层:domain(业务逻辑 — 独立于框架)、data(仓库、API、DB)和presentation(UI、ViewModel)。Domain层使用SOLID原则:用例(SRP)、仓库接口(DIP)、实体类(OCP + LSP)。

根据 Android Developers Guide, 2025SRP 在Android中体现在ViewModel、Repository和Mapper的分离上。OCP — 通过DataSource接口添加新数据源时。LSP — 统一处理来自不同仓库的Result。ISP — 采用CQRS方法(分离读/写仓库)。DIP — 通过Hilt/Koin进行依赖注入。

原则没有它的问题移动项目中的解决方案
SRPActivity超过1000行ViewModel + UseCase + Repository
OCP按支付类型switch-case策略模式:PaymentGateway接口
LSP替换BaseViewModel时出现错误检查子类契约
ISPUnsupportedOperationExceptionReader / Writer分离
DIPViewModel手动创建RetrofitHilt / Koin DI容器

应用SOLID时的常见错误

SOLID错误 通常与过度复杂化代码有关。第一个 — 不考虑上下文地机械遵循原则。将一个UserService类拆分为10个接口和15个类以实现“纯粹”的ISP是过度设计。SOLID是工具,不是目标。第二个错误 — 混淆SRP和“一个方法 = 一个职责”。一个类可以有多个方法,如果它们都属于同一职责领域。

根据 Simple Thread, 2024,第三个错误 — 在Android的ViewModel继承中忽略LSP。如果基础ViewModel期望LiveData,而子类使用StateFlow — 订阅LiveData的客户端代码将收不到更新。第四个 — 为了测试而违反DIP:RepositoryImpl直接创建OkHttpClient实例,使单元测试无法进行。

黄金法则:当SOLID解决实际问题时(频繁变更、测试困难、重复),才应用它。对于简单的CRUD页面,严格遵循所有五项原则是过度的。对于业务逻辑、财务计算和API交互,SOLID是必须的。

SOLID与Clean Architecture的关系

Clean Architecture(Robert C. Martin, 2012)— 在应用程序层级别直接应用SOLID。SRP定义用例边界(每个用例 — 一个类)。OCP通过仓库接口实现(Data层可以更改而不影响Domain)。ISP提供用例的输入/输出边界分离。DIP — 依赖方向指向Domain层内部。LSP确保任何仓库实现可替换而不会破坏用例。

常见问题

用简单的话说,什么是SOLID?

SOLID — 五个编写代码的规则,使代码易于修改、测试和理解。每个字母是一个原则:不要写大类(SRP),不要修改现有代码 — 添加新的(OCP),不要破坏继承者的行为(LSP)等等。

哪个SOLID原则最重要?

SRP(单一职责) 被认为最重要,因为违反它会导致上帝类 — 难以测试和修改的巨大类。然而,没有DIP(依赖反转),代码保持紧耦合,这也很关键。

SOLID在移动开发中是必须的吗?

不是必须的,但对于长生命周期的商业项目非常推荐。对于简单的应用(一个页面,没有业务逻辑),SOLID可能过度。对于50+页面和3+开发人员的项目,SOLID是必要的最低要求。

如果不遵守SOLID会怎样?

后果:类变得“臃肿”(1000+行),一处的更改破坏三处其他,无法编写单元测试,添加新功能需要数周而非数天。随着时间的推移,代码变成“Big Ball of Mud” — 混乱而脆弱。

如何检查项目中是否遵守了SOLID?

遵守的迹象:每个类少于200行,功能更改不影响5+个文件,测试无需mock 10个依赖项即可编写,新开发人员一天内理解结构。SonarQube和detekt等工具有助于检测SRP和DIP的违反。

总结

  • SOLID — 五个OOP原则(SRP、OCP、LSP、ISP、DIP),用于创建灵活且可维护的代码
  • SRP — 每个实体负责一个任务,解决上帝类问题
  • OCP — 通过多态扩展,而不是修改现有代码
  • LSP — 继承者不应破坏基类的行为
  • ISP — 小接口代替通用的“瑞士军刀”
  • DIP — 依赖抽象,在Android中通过Hilt/Koin注入
  • SOLID 对于Clean Architecture和商业移动项目是必须的

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读