第 1 章:依赖方向与系统耦合度
1.1 依赖方向的定义
依赖方向决定架构弹性。正向依赖(数据流与依赖流一致)导致紧耦合。反向依赖(数据流与依赖流相反)实现松耦合和可测试性。
| 方面 | 正向依赖 | 反向依赖 (DIP) |
|---|---|---|
| 数据流 | 从高层流向低层 | 从低层流向高层 |
| 依赖流 | 从高层流向低层 | 高层和低层都流向抽象 |
| 耦合度 | 高(运行时和编译时绑定) | 低(编译时分离,运行时多态) |
| 可测试性 | 低(难以模拟低层模块) | 高(易于通过接口注入模拟对象) |
1.2 模块边界与职责
清晰的模块边界依赖稳定的抽象接口。接口属于高层(调用方),而非低层(实现方)。
核心职责分离:
高层模块:定义业务流程与策略,声明所需接口。
低层模块:实现具体技术细节(数据库、IO、第三方库)。
信任链路:高层信任低层遵守接口契约,低层信任高层定义的接口稳定不变。
高层模块:定义业务流程与策略,声明所需接口。
低层模块:实现具体技术细节(数据库、IO、第三方库)。
信任链路:高层信任低层遵守接口契约,低层信任高层定义的接口稳定不变。
1.3 耦合度量化指标
- 扇出 (Fan-out): 一个模块直接依赖的其他模块数量。扇出高则耦合度高。
- 扇入 (Fan-in): 依赖该模块的其他模块数量。扇入高表示模块被复用,不一定代表高耦合。
- 不稳定性 (Instability): I = 扇出 / (扇入 + 扇出)。值越接近 1,模块越不稳定,越容易被变更影响。
- 抽象程度 (Abstractness): A = 抽象元素数 / 总元素数。稳定的模块应具有高抽象程度。
1.4 选型权衡与风险点
风险点 1: 循环依赖。模块 A 依赖 B,B 依赖 A。导致构建失败或运行时死循环。必须通过引入抽象接口打破循环。
风险点 2: 依赖膨胀。模块泄漏内部依赖到外部,导致不必要的重新编译和部署。
解决策略: 严格依赖方向控制,应用依赖倒置原则 (DIP),强制接口属于客户方。
风险点 2: 依赖膨胀。模块泄漏内部依赖到外部,导致不必要的重新编译和部署。
解决策略: 严格依赖方向控制,应用依赖倒置原则 (DIP),强制接口属于客户方。
1.5 落地步骤与验收标准
- 识别模块间的数据流和信任链路。
- 提取高层模块需要的抽象接口。
- 反转依赖方向:高层依赖接口,低层实现接口。
- 使用依赖注入框架(如 Spring, Guice)或工厂模式组装依赖。
- 设置架构测试(ArchUnit)验证依赖规则。
// 高层模块定义的契约(属主为调用方)
public interface PaymentProcessor {
Result process(Order order);
}
// 低层模块实现契约(依赖倒置)
public class StripeProcessor implements PaymentProcessor {
public Result process(Order order) {
// 具体实现细节
}
}
| 验收项 | 标准 | 检查手段 |
|---|---|---|
| 依赖方向正确性 | 高层模块不直接依赖低层模块的具体类 | 代码审查 / ArchTest |
| 可测试性 | 高层模块可在无低层实现的情况下独立测试 | 单元测试覆盖率 |
| 组件独立性 | 替换低层实现不需要修改高层模块 | 模拟替换测试 |