type
date
slug
category
icon
password
软件设计六大原则 = SOLID 五原则 + 迪米特法则 (LoD)。SOLID 由 Robert C. Martin 提出,聚焦类与接口的结构设计;迪米特法则补充了对象间运行时通信的约束。六者共同服务于"高内聚、低耦合"的核心目标。
# | 六大原则 | SOLID | 一句话 |
1 | 单一职责 (SRP) | S | 一个类只做一件事 |
2 | 开闭原则 (OCP) | O | 对扩展开放,对修改关闭 |
3 | 里氏替换 (LSP) | L | 子类可无缝替换父类 |
4 | 接口隔离 (ISP) | I | 接口小而专,不强迫依赖 |
5 | 依赖倒置 (DIP) | D | 依赖抽象,不依赖具体 |
6 | 迪米特法则 (LoD) | ❌ | 只与直接朋友通信 |
1. 单一职责原则 (SRP)
一个类应该只有一个引起它变化的原因。
职责过多导致高耦合:修改存储逻辑可能意外破坏业务逻辑。将不同变化原因拆分到不同类中,每个类的修改面最小化。
示例 1:嵌入式传感器驱动
TemperatureSensor 只负责读取寄存器值。温度过高的报警判断由 AlarmService 负责。两个变化原因(硬件寄存器变更、报警阈值变更)分属不同类。示例 2:日志系统
Logger 类只负责格式化日志消息。日志写入文件由 FileWriter 负责,写入网络由 NetworkWriter 负责。当需要从文件切换到 syslog 时,Logger 无需修改。2. 开闭原则 (OCP)
软件实体应对扩展开放,对修改关闭。
通过增加新代码实现新功能,而非修改现有代码。核心手段:抽象 + 多态。新增子类而非
if-else 分支。示例 1:Linux 内核 VFS
VFS 定义了
file_operations 抽象接口。新增文件系统(ext4、btrfs)只需实现该接口并注册,内核核心代码无需修改。示例 2:支付系统
定义
PaymentGateway 接口。新增微信支付时,创建 WeChatPay 实现类并注入,原有的支付宝、银联代码不受影响。3. 里氏替换原则 (LSP)
子类必须能替换父类,且不改变程序正确性。
继承必须确保"Is-A"关系的行为一致性。子类不能违反父类的前置条件、后置条件和不变量。
示例 1:矩形与正方形
Rectangle 允许独立设置宽高。如果 Square 继承 Rectangle 并强制 width == height,调用 setWidth() 后 getHeight() 返回值变化,违反父类行为契约。正确做法:提取 Shape 接口,分别实现。示例 2:集合类
ArrayList 和 LinkedList 都实现 List 接口。任何接受 List 参数的函数,传入二者均能正确工作。若某个子类的 add() 方法忽略了元素不添加,就违反了 LSP。4. 接口隔离原则 (ISP)
客户端不应被迫依赖它不使用的方法。
胖接口导致不必要的耦合和重编译。将大接口拆分为多个职责单一的小接口。
示例 1:打印机接口
老式设计把
print(), scan(), fax() 放在一个 IMachine 接口。简易打印机被迫实现 scan() 和 fax() 的空方法。拆分为 IPrinter, IScanner, IFax 三个接口后,简易打印机只需实现 IPrinter。示例 2:Linux 字符设备
file_operations 结构体中有 read, write, ioctl, mmap 等函数指针。驱动只需实现所需的函数,其余置 NULL。内核在调用前检查指针是否非空,天然符合 ISP。5. 依赖倒置原则 (DIP)
高层模块不应依赖低层模块,二者都应依赖抽象。
传统分层:高层 → 低层。倒置后:高层 → 抽象接口 ← 低层。抽象归高层所有,低层负责实现。
示例 1:单元测试中的 Mock
业务代码依赖
IDatabase 接口而非 MySQLClient。测试时注入 MockDatabase,无需启动真实数据库即可验证业务逻辑。示例 2:Linux 内核 Platform 驱动模型
驱动通过
platform_driver 注册,与硬件描述(Device Tree)解耦。内核匹配框架(抽象层)连接驱动和设备。更换硬件平台时,只需修改 DTS,驱动代码不变。6. 迪米特法则 (LoD / Law of Demeter)
一个对象应只与直接朋友通信,不与"朋友的朋友"交谈。
也称最少知识原则。减少对象间的信息链路,避免
a.getB().getC().doX() 式链式调用。调用链越长,耦合面越大。示例 1:门面模式 (Facade)
客户端需要调用子系统的多个类完成一次操作。引入
Facade 类封装子系统交互,客户端只与 Facade 通信,不直接接触子系统内部对象。示例 2:嵌入式消息总线
各模块不直接持有彼此引用,通过
MessageBus 发布/订阅事件。传感器模块发布 TempChanged 事件,报警模块订阅该事件。两者互不知道对方存在。综合示例:Linux 内核驱动架构如何遵循六大原则
Linux 内核的设备驱动模型是六大原则在系统级软件中的经典实践。
各原则在架构中的体现
原则 | 在 Linux 驱动架构中的体现 |
SRP | 驱动只管硬件操作,总线只管匹配,VFS 只管文件抽象。每层职责边界清晰,互不侵入。 |
OCP | 新增硬件支持时,编写新驱动模块并注册,内核核心代码无需修改。 module_init / module_exit 机制天然支持扩展。 |
LSP | 所有字符设备驱动实现 file_operations,用户空间通过统一的 open/read/write 系统调用访问。替换底层驱动对上层透明。 |
ISP | file_operations 中的函数指针可选实现(置 NULL)。只读设备无需实现 write,非 mmap 设备无需实现 mmap。 |
DIP | 驱动依赖内核抽象接口( platform_driver, i2c_driver),不直接操作总线寄存器。硬件描述(Device Tree)与驱动代码解耦。 |
LoD | 用户空间只与 VFS 交互,不感知底层总线和驱动。驱动通过内核 API( devm_* 系列)获取资源,不直接遍历设备树节点链。 |
核心启示:六大原则不是孤立使用的。Linux 内核驱动架构通过分层抽象 + 接口隔离 + 注册机制,将六大原则编织为一个协同体系。每条原则解决一个维度的耦合问题,合力实现了内核对数千种硬件的可扩展支持。
- 作者:felixfixit
- 链接:https://www.felixmicrospace.top/article/software_design_principles
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。




