依赖倒置与依赖规则
§1摘要与结论
稳定策略不应依赖易变细节:通过抽象使高层策略与低层实现都依赖抽象,让源码依赖方向指向更稳定、更抽象的一端。其含义不是高层依赖低层再调用回来,而是双方都依赖一个由稳定侧拥有的抽象——高层定义它需要的能力,低层去实现它。配套的依赖规则要求源码依赖只能由外层指向内层,禁止反向,由此形成单向依赖图,稳定且抽象的核心不被细节牵引,边界可替换,实现细节可独立演化。但把它当铁律并不现实:系统必须依赖大量具体设施,有些具体类型改动极罕见且受严格控制,对它们的依赖不该也无法回避。可容忍的是一层稳定背景,例如操作系统与平台设施;要避开的是团队正在活跃开发、频繁变动的模块。
别名:DIP、dependency rule、依赖倒置 | 英文:Dependency Inversion and the Dependency Rule | 类型:方法原则 / concept
§2局部关系图
§3原理 · 为什么
这条规则被称为让整套架构成立的首要规则,条文是:源码依赖只能向内,指向更高层的策略。禁止项分两层写。其一,内圈代码不得知道外圈的任何事物,特别是不许提及外圈声明的任何具名软件实体,函数、类、变量皆在其列。其二,外圈声明的数据格式也不得被内圈使用;若该格式由外圈框架生成,危险尤甚。几何表达上,同心圈越向内策略层级越高、抽象度越高,最外圈是低层具体细节,最内圈最通用也最高层。执行时会遇到控制流与依赖方向相反的矛盾,例如用例需要回调展示器;解法是让内圈持有输出端口接口、由外圈实现,借助动态多态把源码依赖在边界处逆着控制流安排。作者强调这套手法适用于架构中所有边界的穿越,而不是某一处的特例,因而边界两侧的分工可以被统一地推理与复核。这样形成的单向依赖图让核心不必随外部技术的更替而反复改写,也让核心语义与测试保持稳定。这条规则的价值可以再往下拆一层:当核心不依赖任何具体设施时,核心的行为就能在脱离真实环境的条件下被验证,验证结果也不会因为外部设施更换而需要重写;同时外部设施可以被逐个替换,替换过程中核心不需要同步修改。它还带来一个副作用,即依赖方向反过来暴露了哪些部分更难改动——被依赖的一侧因为承担了契约,一旦调整就会波及所有依赖者,所以这类位置应当尽量放稳定且抽象的内容,而把易变的内容推到依赖链的末端。由此推出四条编码纪律:不引用易变具体类、不从易变具体类派生、不覆写具体函数、绝不提及易变具体物的名字。把定义中的稳定与易变分开看,可以更清楚地理解它要解决的问题:抽象化的目的不是让代码里不出现具体类型,而是让那些会随需求改动而频繁变化的类型不成为高层逻辑的直接支撑。因此判断某个依赖是否需要倒置,关键不在它是否具体,而在它是否由本方团队频繁修改;对不由本方控制、改动节奏极慢的外部设施,直接依赖并不会带来多少维护痛苦。这条界线本身也是会移动的,今天的稳定设施明天可能被替换,今天的自研模块也可能沉淀为长期不动的部分,所以需要周期性重估,而不是一次判定后长期沿用。
§4机制 · 如何运作
倒置的机械细节是:几乎所有语言里创建一个对象都要求对该对象的具体定义产生源码依赖,抽象工厂正是为此引入——让应用通过服务接口使用实现,再经工厂接口的创建方法取得实例,由工厂实现类负责实例化并以接口类型返回。架构边界分隔抽象侧与具体侧,全部源码依赖跨线时朝同一方向指向抽象侧,而控制流跨线方向与之相反,这正是「倒置」一词的由来。违反无法彻底清除,但可收拢进少数具体组件;多数系统至少有一个这样的组件,通常就是含入口函数的那一个。动态类型语言里这类纯接口组件根本不存在,依赖结构因此简单得多。在组件层面,两条原则合成一条可计算判据:不稳定度是出向依赖数与总依赖数之比,等于一表示无人依赖它而它依赖别人,等于零表示被人依赖而自身不依赖别人,原则要求依赖方向上指标递减;稳定抽象原则要求组件多稳定就多抽象,因为最大稳定的组件若不含抽象便再也无法扩展。二者合并即组件层面的依赖倒置:依赖朝稳定方向走,稳定蕴含抽象,故依赖朝抽象方向走。与类不同,组件允许部分抽象、部分稳定,因此度量上给出抽象度与一条主序列,并用偏离距离量化;两个排斥区分别是稳定又具体的痛苦区,以及最抽象却无人依赖的无用区。策略模式则给出同一手法的轻量版本。度量上还可用均值、方差与随时间的控制线筛查异常组件。
§5工程 · 怎么落地
落地结构把代码库分成内侧与外侧:内侧是领域概念,外侧是界面、数据库、第三方集成等对外交互,主规则是外侧依赖内侧、永不反向,内侧类型还应改用通用领域语言称呼。落地手段的核心是把依赖方向交给编译器。若把所有类型都标成公开,包就退化成文件夹式的分组而非封装机制,因为公开类型从代码库任何位置都可用,各种架构风格在语法上会变得完全一样;此时只剩两条软弱手段——口头纪律加代码评审,预算与截止期一紧就失效;或编译后用静态分析手段跑「某包不得访问某包」式的规则,反馈回路偏长。可行的折中是按组件分包,把同一粗粒度组件相关的职责收进一个包,对外只留一个干净接口,内部再分层。翻译职责则落到接口签名一级:数据库网关是覆盖应用所需全部增删改查操作的多态接口,方法按业务语义命名,由数据库层里的类实现,SQL 不许出现在用例层。数据映射器被单独纠名,归入数据库层,构成网关与数据库之间的又一层边界。边界处的实现通常被写成几乎不含判断的谦卑对象:它们只做转换与转发,把决策留给内侧。这样做的回报是验证成本下降,因为需要仔细检验的逻辑集中在少数位置,边界层的行为接近透传,出错时也容易定位。需要提防一种常见退化——为消除依赖而引入抽象,却让具体类型顺着接口的参数与返回值回到内侧,于是依赖只是换了个形式,实际问题没有解决。
§6权衡卡
收益侧写在架构目标里:外部部件过时后可以以最小的麻烦替换,系统内在可测试。
倒置应聚焦在真正稳定的核心与真正易变的外部之间,对内部稳定的实现不必强加接口;把倒置套用于一切依赖,只会稀释它的价值并增加负担。
成本侧有三处明写。
其三,完整边界的前期与维护开销都很高,部分边界各有代价且会退化。
把两侧对齐可得到一条判断次序——先问该处是否真的会变,再决定是否为它付出接口与间接层的代价;为不会变的位置提前倒置,只换来接口泛滥、间接层与调试成本。
与之相对的另一侧代价是迭代速度:每一层间接都让调用链变长,排查问题时需要在更多位置之间来回,新人也要花更多时间弄清一次请求经过了哪些环节。
因此倒置适合用在变化真实且代价高昂的位置,用在变化罕见的位置只会同时抬高理解与调试成本。
- 依赖管理指标只是对某个好模式的符合度测量,而那个模式来自经验:指标不是神,它只是与一个任意标准的比对,至多是不完美的,可用而已。
- 其一,只应对易变的具体元素做倒置,稳定的平台背景可以容忍。
- 其二,抽象而无依赖者的组件落在无用区,是无人实现的残留抽象类,属于净损耗。
- 还需提防抽象设计不当:若接口签名里写进了外层的表结构或序列化形态,倒置就只停留在形式上,底层改动仍会沿参数与返回值回流到高层,此时应先修正抽象边界,而不是继续增加接口。
权衡原文仅按显式标记词归栏引用(收益 / 代价 / 反例),其余句子按原序列为「原文要点」;内容逐字引用未改写。
§7学习路径条
§8案例带
网申 v3 多模块解耦
一套面向高可扩展性的网申信息速查扩展架构:以 schema.js 为唯一数据驱动源,把数据模型、存储、纯函数与渲染组织为严格单向依赖的共享层,供 popup 与页面浮层双容器复用,并先立跨模块接口契约。
preset 通道耦合分析
对 harness「委派工具层→续用管理器→preset 服务→MCP 服务器」这条通道做耦合分析:识别契约、服务定位、数据契约、组合语义、路径与装配一致性六类耦合点,定位「静默丢弃特化」与「装配漂移」两处实质缝隙。
dsh-manager 插件深度分析
以「编译依赖、运行时注入、组合装配」三硬依据加源码行号,对第三方插件做依赖、功能、需求溯源与设计问题裁决,并复用上游 DAG 知识库节点标识,使分析结果可与主图互查。
dsh 插件依赖增量定位
以官方插件 DAG 为蓝本,对一个官方清单之外的全新社区插件做功能定位与依赖位置增量分析,产出可下钻交互依赖图与多形态文档的源码级架构逆向案例。
去中心化聊天架构逆向
对一个仅有 58 份设计文档、尚未实现的开源去中心化聊天项目做架构逆向:从设计文本提炼 18 个模块及其依赖关系,重建模块依赖 DAG 与三条核心数据流 DAG,并输出全景理解与深度需求分析。
网申填写扩展逆向架构
对 Chrome MV3 网申自动填写扩展的逆向架构分析:梳理 popup、content、background、options 四模块职责,解剖 12 个站点适配器、双通道消息协议与隐私脱敏机制,并评估其零依赖零构建选型。
网申 v2 深度重构
一次由「改一处漏多处」症状触发的深度重构:定位到接口定义散落 12 个文件、存在 7 种接口形态的根因后,以单一契约源、dispatch 消息表与能力注册中心重建三层契约系统,恢复可审计与可校验性。
harness 源码架构解析
对 deepseek-harness 官方源码做分层解析:镜像官方架构文档,按九个层与九十余模块建立文档集群,产出跨层联动矩阵与七条关键路径,并钉住「模型可见即已记录」、能力缝三角等设计不变量。
Hermes 七层架构总览
对八千余文件的工业级 AI Agent 项目做七层架构总览逆向,揭示其「越用越聪明」的自进化闭环如何在层间传导的控制型架构案例。
MCP 四件套选型对比
对四个同类 MCP 服务器做源码级能力与成本对比,并以常驻 token 预算为轴给出分级接入策略的技术选型实战案例。
Claude Code 架构逆向
以社区逆向源码、系统提示词与官方文档三源交叉验证,还原一个商业 Agent 的查询引擎、子Agent、工具与权限体系的逆向分析案例。
Open Code Review 图谱
对阿里开源 AI 代码审查 CLI 做源码级解析并构建「知识 + 实践资产 + AI 索引」三合一知识图谱的工程案例。
AI Agent 架构 DAG 复盘
对四个工作日内独立完成三个异构对象 DAG 分析的方法论复盘,总结「采集→建模→生成→门控→交付」可复用管线与关键教训。
§9信源与核验
§10原始关系清单
展开:本节点的全部原始关系边(5 条,含边 ID 与方向)
| 关系 | 方向 | 对端节点 | 说明 |
|---|---|---|---|
| 案例印证 | ← CAS-X-03 | 网申 v3 多模块解耦 E-946 | 印证:动态导入切断循环依赖,容器依赖抽象接口 |
| 案例印证 | ← CAS-X-05 | preset 通道耦合分析 E-952 | 张力:服务定位的隐式依赖与静默短路削弱依赖倒置 |
| 案例印证 | ← CAS-X-08 | dsh-manager 插件深度分析 E-960 | 违背:依赖声明不实(声明一项却消费多项)削弱依赖倒置 |
| 案例印证 | ← CAS-X-10 | dsh 插件依赖增量定位 E-969 | 印证:以装配与注入关系还原依赖方向 |
| 前置依赖 | ← ESS-G-03 | 边界与接口隔离 E-06 | 先划定边界,才能约束依赖方向 |