💡
  • 模块化的九个原则
  • 对给定的示例,发现其所违反的原则,并进⾏修正

面向对象中的模块

最重要的是类.
对于类来说, 类的供接口就是其所有公有的成员变量和成员方法, 可以被别的类直接访问, 代表该类愿意与他人合作的一个协议; 类的需接口则是其在实现中使用到的其他类及其相关协议.

模块化的九个原则

💡
对给定的示例要能发现其所违反的原则并修正
面向对象方法下的耦合主要可分为访问耦合与继承耦合.
模块化设计中常见的设计原则:
编号
原则
中文名称
核心目标
1
Global Variables Bad
全局变量有害
减少隐式耦合
2
Be Explicit
显式优于隐式
提高代码清晰度
3
Do Not Repeat
不要重复代码
降低维护成本
4
Programming to Interface
面向接口编程
解耦依赖, 支持替换和扩展
5
Law of Demeter
迪米特法则
减少模块了解彼此的细节
6
Interface Segregation
接口隔离原则
接口要细粒度
7
Liskov Substitution
里氏替换原则
保证继承正确性与一致性
8
Favor Composition
优先组合而非继承
灵活扩展, 避免继承弊端
9
Single Responsibility
单一职责原则
模块关注单一功能, 易维护

降低访问耦合

notion image

全局变量有害(Global Variables Consider Harmful)

💡
针对公共耦合
含义:尽量避免使用全局变量.
理由
  • 全局变量会引起模块之间的隐式耦合;
  • 修改难以追踪, 调试困难;
  • 破坏封装性, 使代码难以维护.
示例:一个模块修改了全局状态, 另一个模块也依赖该状态, 结果可能不一致, 甚至产生错误.

显式优于隐式(To be Explicit)

💡
针对隐式耦合
含义:程序的行为应尽可能明确、可读, 不应隐藏在复杂逻辑或默认规则中.
理由
  • 增强可读性和可维护性;
  • 减少误解和错误行为.
示例:函数的输入参数应清晰地说明用途, 而不是依赖外部状态或隐式数据.

不要重复代码(Do not Repeat)

💡
针对重复耦合
也称 DRY 原则(Don’t Repeat Yourself)
含义:相同的逻辑应封装成一个模块或函数, 避免重复代码.
理由
  • 降低维护成本;
  • 修改时只需改一处, 避免遗漏;
  • 增强一致性.
示例:计算折扣的逻辑不要在多个地方重复出现, 应提取为一个公共函数.

面向接口编程(Programming to Interface)/ 契约式设计(Design by Contract)

💡
针对访问耦合
含义依赖接口(抽象)而不是实现类(依赖倒置原则DIP).
理由
  • 解耦, 提高灵活性;
  • 更容易替换或扩展实现;
  • 有助于单元测试和依赖注入.
示例:PaymentProcessor 接口定义支付操作, 而 AliPay 和 WeChatPay 是具体实现.

迪米特法则 / 最少知识原则(The Law of Demeter)

💡
针对访问耦合
降低控制耦合的深度
含义:一个模块/对象只应该与直接的朋友通信, 不应该了解太多“陌生对象”的内部细节, 限制调用链长度在2以内.
具体说法
只调用以下之一:
  • 本对象的方法;
  • 参数对象的方法;
  • 直接创建的对象的方法;
  • 直接成员变量的方法(但不要再访问其成员的成员).
理由
  • 降低耦合;
  • 防止对象过度依赖其他模块的结构.
    • 级联调⽤的问题: 难以应对变更+代码难理解. 应对的⽅法: 采⽤委托式调⽤, 限制单次调用链的深度.
示例(违反法则)
order.getCustomer().getAddress().getCity() 就是典型的“深入访问”, 应避免.
当一个人想要遛狗, 千万不要命令狗的腿去走, 而是人命令狗, 让狗去命令它自己的腿.

接口隔离原则(Interface Segregation Principle)

💡
针对访问耦合
含义:客户端不应依赖它不需要的接口. 换言之, 接口应该小而专一, 而不是庞大而笼统.
理由
  • 降低实现类的负担;
  • 避免空实现或无关依赖(即不必要的访问耦合).
示例
不要定义一个 IMachine 接口包含 print()、scan()、fax(), 而应拆分为 IPrinter、IScanner 等更细粒度接口.

降低继承耦合

notion image

里氏替换原则(Liskov Substitution Principle)

💡
针对继承耦合
含义子类对象应该能够替换父类对象, 并保持原有行为的正确性.
理由
  • 确保继承关系成立;
  • 避免多态行为异常.
不使用继承时耦合度为N, 一棵满足里氏替换原则的继承树的耦合度为1, 即在符合LSP的前提下, 继承树内部的类间耦合是可以忽略的, 有效降低了耦合; 而不符合LSP时使用继承甚至还增加了耦合(调用者需要区分父类和所有子类, 耦合度N+1).
违反示例
Bird 是 Animal, Penguin 是 Bird, 但 Penguin.fly() 不成立, 说明设计不当.

优先使用组合而非继承(Favor Composition Over Inheritance)

💡
针对继承耦合
在希望复用代码又难以满足里氏替换原则时, 一般用组合代替继承
含义:优先通过组合(“有一个”)关系来扩展功能, 而不是通过继承(“是一个”)关系.
理由
  • 继承耦合紧密, 容易导致“脆弱基类问题”;
  • 组合更灵活, 可在运行时动态切换行为.
示例
用 Car 拥有一个 Engine 对象, 而不是 Car 继承自 Engine.

提高内聚

方法内聚, 类的内聚, 子类和父类的继承内聚

单一职责原则(Single Responsibility Principle)

💡
提高内聚
含义:一个模块(类/函数)应该只负责一类功能, 不要同时处理多个职责.
理由
  • 提高可维护性和可测试性;
  • 修改时影响范围小.
示例
一个类如果同时处理“用户登录逻辑”和“日志记录逻辑”, 就违反了单一职责, 应拆分为两个类.
 
Loading...