第四章:依赖方向与稳定抽象原则
在模块边界设计中,依赖关系不仅仅是代码层面的引用,更是架构信任链路的载体。**稳定依赖原则(SDP)**、**稳定抽象原则(SAP)** 与 **依赖倒置原则(DIP)** 共同构成了管理模块间依赖方向的黄金三角。
架构要点:依赖关系是一种编译时契约。SDP 和 SAP 定义了哪些模块应该成为这种契约的承载者。DIP 则给出了实现这种契约的具体编码模式。
4.1 依赖方向判定:不稳定度(I)的计算
SDP 的核心思想是:不稳定的模块(易变)应该依赖稳定的模块(不变),反之则要警惕。我们通过计算模块的 不稳定度(I) 来量化这种风险。
- 传入依赖(Ca): 有多少外部模块依赖你(扇入)?
- 传出依赖(Ce): 你依赖了多少外部模块(扇出)?
公式:I = Ce / (Ca + Ce) —— I 的取值范围是 [0, 1]。
| 模块类型 | I 值范围 | 依赖路径风险 | 边界设计建议 |
|---|---|---|---|
| 基础框架 / 工具库 | 0 ~ 0.1 | 极高:所有人依赖它,它不能轻易变 | 必须抽象,采用外观模式封装变化 |
| 业务领域层 | 0.1 ~ 0.3 | 较低:有一定的传入依赖,无实现细节 | 作为依赖目标,应全为接口 |
| 具体实现类 | 0.7 ~ 1.0 | 高:经常随配置或需求变更 | 应仅被组合根引用,或被注入 |
| 循环依赖双方 | 计算失真 | 致命:无法单独发布或测试 | 必须打破重构,引入中间接口 |
4.2 稳定性与抽象度的平衡(SAP)
SAP 指出:一个模块如果非常稳定(I=0),则它必须是抽象的(A≈1),以便能够被扩展;如果一个模块非常不稳定(I=1),则它可以是具体的(A≈0)。
主序列距离(D):D = |A + I - 1|。最理想的组件落在“主序列”上(D=0)。D 值过大(如超过0.5)的组件通常预示着设计问题。
风险识别案例:假设有一个
ConfigLoader 模块,所有模块都依赖它获取配置(Ca 高,I=0.1)。但它直接读取具体 JSON 文件(Na 低,A=0.1)。则 D = |0.1+0.1-1| = 0.8。这个模块落入了“痛苦区”:它非常具体且非常稳定。任何配置格式的变更都会导致所有依赖模块重新编译。
标准做法:对这个 ConfigLoader 提取 ConfigProvider 接口(提高 A 值),并利用依赖注入(DIP)让具体实现离开主链路。
4.3 依赖倒置在边界上的落地
DIP 是实施 SDP 和 SAP 的战术手段。它要求高层次模块不应依赖低层次模块,两者都应依赖抽象。
- 信任链路: 方向从具体的生产者指向抽象的消费者(接口)。在大型业务系统中,我们通常会在多个微服务之间建立这种信任,让调用方依赖服务端的接口包,而非实现包。
- 数据流与控制流: 数据流顺着依赖方向流动(调用者传入数据),控制流则逆着依赖方向流动(调用者控制流程)。
- 选型权衡: 引入接口层会增加系统复杂度和间接性。对于团队内部、几乎没有变化的模块,过度抽象反而降低开发效率。通常建议:跨团队边界、易变逻辑、外部集成点必须使用 DIP。
边界设计陷阱:不要在模块边界上使用“漏失抽象”。如果创建的接口(如 IBusinessService)只有一个实现且几乎不变,则这个抽象是无效的。它占用了维护成本却没有带来灵活性。务必将抽象服务于易变点。
4.4 落地步骤与验收标准
在具体项目中落实依赖方向与稳定抽象原则,建议按以下步骤执行,并在架构看板中持续监控。
- 依赖扫描与矩阵化: 使用 Architecture Eval 或定制脚本,生成全量模块的依赖矩阵。标记所有违反单向依赖的循环。
- 计算度量: 对每个模块计算 Ca、Ce、I、A、D 五元组。生成散点图,标出位于“痛苦区”(D>0.5)的模块。
- 识别变与不变: 访谈各模块所有者,标记出易变业务逻辑和稳定基础设施。
- 制定重构清单:
- 对落在痛苦区的稳定具体模块:提取接口。
- 对跨越边界的直接 new:改为工厂或 DI 注入。
- 对循环依赖:引入中间层或事件总线。
- 设置门禁: 在 CI 中引入依赖规则引擎,禁止新模块的 D 值超过团队容忍阈值(如 < 0.6)。
验收标准
| 维度 | 验收指标 | 验证方式 |
|---|---|---|
| SDP 合规 | 无反向依赖;I 值高的模块不依赖 I 值低的模块 | 依赖结构矩阵(DSM) |
| SAP 合规 | 所有稳定模块(I<0.3)的 A 值 >0.7 | 自动化静态检查 |
| DIP 实施 | 所有跨边界调用必须通过接口;禁止直接引用实现类 | Code Review + ArchUnit |
| 信任链路 | 模块间有明确的契约包(API 包),无实现泄漏 | 包依赖分析 |
持续集成建议:将依赖规则检测接入 GitLab CI 的基本门禁。当出现 D > 0.5 的提交时,流水线给出黄色警告;当出现循环依赖或依赖方向错误时,直接红色阻断。