我将用简洁易懂的方式介绍面向对象设计的七大核心原则(SOLID + 合成复用/迪米特法则),通过定义 + 实际案例 + 代码对比帮助理解它们的核心思想。
1. 单一职责原则 (SRP - Single Responsibility Principle)
定义:一个类只负责一件事
核心思想:高内聚、低耦合
优点:代码更清晰,修改风险低,复用性高。
缺点:过度拆分可能导致类数量爆炸,增加复杂度。
违反案例:
1 | // 糟糕设计:一个类同时处理订单逻辑和发送邮件 |
正确实现:
1 | // 拆分职责 |
实际意义:修改邮件发送方式时,不会影响订单核心逻辑
2. 开闭原则 (OCP - Open-Closed Principle)
定义:软件实体应对扩展开放,对修改关闭
核心思想:通过抽象和继承/组合实现扩展
优点:系统稳定,新增功能灵活。
缺点:需要提前设计抽象层,可能增加前期工作量。
违反案例:
1 | // 硬编码折扣类型,新增类型需修改源码 |
正确实现:
1 | // 抽象折扣策略 |
实际意义:系统扩展时无需修改已有稳定代码,降低风险
3. 里氏替换原则 (LSP - Liskov Substitution Principle)
定义:子类必须能够替换父类且不破坏程序逻辑
解释: 任何基类可以出现的地方,子类一定可以出现。里氏替换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏替换原则是对实现抽象化的具体步骤的规范。
优点:保证继承体系的可靠性,减少隐藏Bug。
缺点:过度约束可能导致冗余代码(如强制重写父类方法)。
核心思想:继承关系应确保行为兼容性
违反案例:
1 | class Rectangle { |
正确实现:
1 | // 通过接口隔离形状类型 |
实际意义:避免因继承导致不可预知的错误
4. 接口隔离原则 (ISP - Interface Segregation Principle)
定义:客户端不应被迫依赖它不使用的接口
核心思想:接口粒度细化,避免“胖接口”,使用多个隔离的接口,比使用单个接口要好
优点:减少代码臃肿,降低依赖风险。
缺点:接口过多可能增加管理成本。
违反案例:
1 | // 臃肿的 Worker 接口 |
正确实现:
1 | // 拆分接口 |
实际意义:减少不必要的依赖,提高系统灵活性
5. 依赖倒置原则 (DIP - Dependency Inversion Principle)
定义:高层模块不应依赖低层模块,二者都应依赖抽象(开闭原则的基础)
核心思想:面向接口编程,解耦模块关系
优点:解耦代码,便于替换底层实现。
缺点:需要额外设计抽象接口,小型项目可能显得繁琐。
违反案例:
1 | // 高层直接依赖具体数据库 |
正确实现:
1 | // 依赖抽象 |
实际意义:提升代码可测试性和可维护性
6. 合成复用原则 (CRP - Composite Reuse Principle)
定义:优先使用对象组合,而不是继承来达到复用目的
核心思想:通过“黑盒复用”降低耦合度
优点:更灵活,避免继承的强耦合。
缺点:对象数量增多,管理关系可能复杂。
案例对比:
1 | // 错误:通过继承复用 |
实际意义:避免继承层次过深导致的“脆弱的基类”问题
7. 迪米特法则 (LoD - Law of Demeter)
定义:一个对象应尽可能少地了解其他对象
核心思想:降低类之间的耦合度
优点:降低耦合,提高模块独立性。
缺点:过度遵守可能增加大量中间类,调用链变长。
违反案例:
1 | class Order { |
正确实现:
1 | // 在Order中封装所需信息 |
实际意义:减少依赖链断裂风险,提高代码健壮性
总结:七大原则的核心理念
原则 | 关键目标 | 一句话记忆 |
---|---|---|
SRP | 功能聚焦 | 一个类只做一件事 |
OCP | 易于扩展 | 扩展不修改旧代码 |
LSP | 继承安全 | 子类能顶替父类 |
ISP | 接口精简 | 不用就别强迫我 |
DIP | 解耦依赖 | 依赖抽象不依赖具体 |
CRP | 灵活复用 | 组合优于继承 |
LoD | 减少知晓 | 你的事我不多问 |
实际应用技巧:当发现以下代码症状时,回顾对应原则:
- 频繁修改某个类 → 违反SRP/OCP
- 子类方法抛出
UnsupportedOperationException
→ 违反LSP - 接口有大量空实现 → 违反ISP
- 单元测试难写 → 违反DIP
- 继承层次超过3层 → 违反CRP
- 调用链超过两个点(如
a.getB().getC()
) → 违反LoD