---
title: "SOLID：面向对象设计的五条基本原则"
description: "SOLID 把面向对象设计中常见的耦合问题归纳成五条原则。它关注职责、依赖和替换关系，目的是让代码更容易理解、修改和测试。"
date: "2026-08-21"
type: "notes"
kind: "note"
tags:
  - 软件工程
  - 软件架构
  - 面向对象
---

**SOLID** 是五条面向对象设计原则的首字母缩写。它们分别是单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。

这五条原则常被放在一起讲，却不是一套必须逐条打卡的编码规范。它们试图回答几个相近的问题。一个类为什么会变得难改，模块之间为什么互相牵连，替换一个实现为什么会破坏调用方，以及抽象什么时候真的有用。

## 这个缩写从哪里来

Robert C. Martin 在 2000 年发表的论文《Design Principles and Design Patterns》中讨论了面向对象设计和软件腐化问题，也整理了后来成为 SOLID 的前五条设计原则。SOLID 这个缩写据 Wikipedia 记录是在 2000 年代中期由 Michael Feathers 提出的。

所以要把两件事分开看。五条原则的内容早于 SOLID 这个词，缩写只是帮助人记住它们。它们也主要针对面向对象程序设计，并不等于所有软件架构都必须采用继承、接口和类层次结构。

## S，单一职责原则

单一职责原则（Single Responsibility Principle，SRP）常被简化为“一个类只做一件事”。更准确的说法是，一个模块应该只有一个引起变化的原因。

这里的“原因”通常来自不同的业务角色或变化来源。一个 `Invoice` 类既负责计算账单，又负责生成 HTML，再负责把结果发送给客户。它会被三类变化同时牵动。账单规则、页面格式和邮件服务只要有一处变化，这个类都可能被修改。

把它拆成账单计算、文档渲染和通知发送，目的是把变化边界分开。这样做以后，修改页面格式不会让账单计算也跟着重新测试。

SRP 也容易被用得过度。一个只有几行代码的简单类，若为了追求“绝对单一”而拆成很多包装层，理解成本会先上升。判断一个模块是否承担了太多职责，要看它是否因为互不相关的变化反复修改。

## O，开闭原则

开闭原则（Open-Closed Principle，OCP）要求软件实体对扩展开放，对修改关闭。这里的实体可以是类、模块或组件。新增一种行为时，理想情况是通过新增实现来完成，而不是反复改动已经稳定的核心逻辑。

例如，订单系统需要支持多种折扣规则。若每增加一种折扣都要在一个巨大 `if` 语句里加入分支，原有代码会不断变动，测试范围也随之扩大。把折扣规则表达成可替换的策略，新增规则时只需要添加一个实现，就更接近开闭原则。

这条原则并不要求提前为所有想象中的变化建立扩展点。过早设计一套插件系统，可能比修改一个简单函数付出更多成本。真正值得稳定下来的扩展边界，通常来自已经出现的第二种实现，或者来自明确而重要的变化方向。

## L，里氏替换原则

里氏替换原则（Liskov Substitution Principle，LSP）来自 Barbara Liskov 1987 年关于数据抽象的研究。它的核心意思是，使用基类或抽象类型的代码，应该能够使用它的子类型，而不需要知道自己换了一个实现。

经典的反例是把正方形当成长方形的子类。长方形接口允许分别修改宽和高，正方形却要求两者始终相等。调用方按照长方形的契约修改其中一个值时，正方形会产生出乎意料的结果。类型关系在语法上成立，行为契约却没有成立。

里氏替换原则要看的重点是子类型是否遵守父类型对输入、输出和状态变化的承诺。子类型不能无故拒绝父类型允许的输入，也不能返回调用方无法处理的结果，更不能在调用方预期安全的操作中突然改变基本语义。

## I，接口隔离原则

接口隔离原则（Interface Segregation Principle，ISP）认为，客户端不应该被迫依赖自己不会使用的方法。

假设一个 `Worker` 接口同时包含打印、扫描、传真和复印方法。只需要打印的客户端也必须依赖完整接口，这会带来不必要的耦合。把它拆成更小的能力接口，打印客户端只依赖打印能力，接口变化的影响范围就会更小。

接口隔离并不等于把每个方法都做成一个接口。接口太碎会制造大量名称、文件和间接调用。好的接口通常围绕客户端真正需要的能力组织，而不是围绕实现者拥有的全部功能组织。

## D，依赖倒置原则

依赖倒置原则（Dependency Inversion Principle，DIP）包含两层意思。高层模块不应该依赖低层模块，两者都应该依赖抽象。抽象不应该依赖细节，细节应该依赖抽象。

例如，订单服务直接创建 MySQL 客户端时，业务规则就和具体数据库绑定在一起。让订单服务依赖一个表达存取需求的接口，再由数据库适配器实现这个接口，业务规则就可以在不更换的情况下接受内存仓储、测试替身或另一种数据库实现。

依赖倒置常与依赖注入一起出现，但两者不是同一个概念。依赖倒置是设计方向，依赖注入是把具体实现传给对象的一种实现方式。一个项目可以通过构造函数注入实现依赖倒置，也可以在其他组合位置完成依赖组装。

## 五条原则怎样放在一起看

SRP 关注变化原因，OCP 关注变化方式，LSP 关注替换时的行为，ISP 关注接口大小，DIP 关注依赖方向。它们互相联系，却解决不同问题。

在一个具体模块里，先观察变化和依赖，再决定是否需要抽象，通常比从 SOLID 的字母开始设计更稳妥。一个可读的具体实现，往往比一组没有真实替换需求的接口更适合作为起点。需求出现以后，再通过测试和重构稳定边界。

SOLID 也不保证系统一定简单。接口、继承、工厂和依赖注入都可能增加间接层。Reddit 和 Hacker News 上关于这些原则的讨论经常提到同一个风险，开发者把 SOLID 变成检查清单以后，可能为了满足原则而制造单方法接口、空洞抽象和多余的包装类。

这反映了设计原则脱离问题使用的风险。原则的价值在于帮助人看见耦合和变化，最终判断仍然要回到代码的规模、需求的稳定程度和维护者的理解成本。

## 关联词

- **SRP**，单一职责原则，关注模块是否被不同的变化来源同时牵动。
- **OCP**，开闭原则，关注新增行为时能否减少对稳定代码的修改。
- **LSP**，里氏替换原则，关注子类型是否遵守父类型的行为契约。
- **ISP**，接口隔离原则，关注客户端是否被迫依赖不用的接口成员。
- **DIP**，依赖倒置原则，关注高层策略和低层细节之间的依赖方向。
- **依赖注入**，把依赖对象从外部传入的一种实现技术，常用于落实依赖倒置。
- **重构**，在不改变外部行为的前提下改善内部结构，是逐步应用这些原则的重要手段。

## 小结

SOLID 是一组观察面向对象设计的概念工具。它提醒我们留意变化边界、行为契约、接口大小和依赖方向。学习它的重点是知道代码为什么难改，以及哪种边界能让下一次修改更局部。

## 参考资料

- [Wikipedia SOLID](https://en.wikipedia.org/wiki/SOLID)
- [Robert C. Martin 的 Design Principles and Design Patterns](https://web.archive.org/web/20230101000000/https://web.archive.org/web/20150906172215/http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf)
- [Reddit 上关于 SOLID、DRY 和 KISS 的讨论](https://www.reddit.com/r/learnprogramming/comments/umr931/s_o_l_i_d_d_r_y_and_k_i_s_s/)
- [Hacker News 上关于可修改代码的讨论](https://news.ycombinator.com/item?id=22593285)
