mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1252 字
3 分钟
依赖图(Dependency Graph)
2026-06-13

一、为什么需要依赖图#

想象你正在维护一个大型 monorepo,里面有几十个包互相依赖。某天你改了底层工具库,发布时随手写了个脚本按目录顺序逐个构建。结果上层包先构建了,引用的还是旧版底层代码,线上直接报错。

问题出在哪?你缺少一张「谁依赖谁」的地图。没有这张地图,构建顺序只能靠人脑记忆和文档维护,而人脑和文档都会过时。更糟糕的是,如果有人不小心引入了循环依赖——A 依赖 B,B 又依赖 A——构建系统会陷入死循环,而你可能要到部署时才发现。

依赖图要解决的就是这类问题:把任务之间的先后约束显式地建模为有向无环图(DAG),用拓扑排序算出合法的执行顺序,并在构建阶段就检测出循环依赖,而不是等到运行时才崩溃。

二、现实类比#

大学课程目录里的先修课表。你不能没上「基础物理」就去选「高级物理」。教务系统把每门课当作节点,先修关系当作有向边,从前置课程跟到高级课程,确保你不跳过任何必修步骤。如果某门课同时是另一门课的先修和后修,那就是循环——教务系统会直接拒绝这种排课。

三、核心思想#

依赖图将每个任务表示为节点,排序约束表示为有向边。addEdge(A, B) 表示「A 必须在 B 之前完成」——A 是 B 的前置条件。Kahn 算法反复移除入度为 0 的节点,产生一个每个前置条件都在其依赖者之前的合法顺序。

flowchart LR wash --> dry dry --> fold wash --> fold

上图中,wash 入度为 0 最先执行,dry 次之,fold 最后。注意 wash 到 fold 的直接边虽然冗余(wash → dry → fold 已隐含),但显式声明不会影响排序结果。

属性
添加节点/边O(1)
拓扑排序O(V + E)
循环检测内置于拓扑排序(剩余节点 = 循环)
空间O(V + E) 邻接表

四、变体与对比#

模式关系区别
脏标记(Dirty Flag)脏传播沿依赖边标记下游节点依赖图管执行顺序,脏标记管重计算时机
注册表(Registry)注册表追踪组件元数据注册表管发现,依赖图管关系
访问者(Visitor)依赖图上的遍历分发访问者是遍历策略,依赖图是数据结构
迭代器(Iterator)拓扑迭代生成惰性节点序列迭代器是访问接口,依赖图是存储结构

五、多语言实现#

5.1 Go 实现#

package depgraph
import (
"fmt"
"sort"
)
// DependencyGraph 用邻接表表示的有向无环图
type DependencyGraph struct {
adj map[string][]string
}
func New() *DependencyGraph {
return &DependencyGraph{adj: make(map[string][]string)}
}
// AddEdge 添加一条依赖边:from 必须在 to 之前完成
func (g *DependencyGraph) AddEdge(from, to string) {
g.adj[from] = append(g.adj[from], to)
// 确保目标节点也在图中
if _, ok := g.adj[to]; !ok {
g.adj[to] = nil
}
}
// TopologicalSort 基于 Kahn 算法的拓扑排序,检测循环依赖
func (g *DependencyGraph) TopologicalSort() ([]string, error) {
inDegree := make(map[string]int)
for node := range g.adj {
inDegree[node] = 0
}
for _, deps := range g.adj {
for _, dep := range deps {
inDegree[dep]++
}
}
// 收集入度为 0 的节点作为起始
var queue []string
for node, deg := range inDegree {
if deg == 0 {
queue = append(queue, node)
}
}
sort.Strings(queue) // 确定性输出
var result []string
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
result = append(result, node)
for _, dep := range g.adj[node] {
inDegree[dep]--
if inDegree[dep] == 0 {
queue = append(queue, dep)
}
}
}
if len(result) != len(g.adj) {
return nil, fmt.Errorf("检测到循环依赖")
}
return result, nil
}

5.2 TypeScript 实现#

// 依赖图:用邻接表管理任务间的依赖关系
class DependencyGraph<T> {
private adjacency = new Map<T, Set<T>>();
addNode(node: T): void {
if (!this.adjacency.has(node)) {
this.adjacency.set(node, new Set());
}
}
// 添加依赖边:from 必须在 to 之前
addEdge(from: T, to: T): void {
this.addNode(from);
this.addNode(to);
this.adjacency.get(from)!.add(to);
}
// Kahn 算法拓扑排序,内置循环检测
topologicalSort(): T[] {
const inDegree = new Map<T, number>();
for (const node of this.adjacency.keys()) inDegree.set(node, 0);
for (const [, deps] of this.adjacency) {
for (const dep of deps) {
inDegree.set(dep, (inDegree.get(dep) ?? 0) + 1);
}
}
const queue: T[] = [];
for (const [node, degree] of inDegree) {
if (degree === 0) queue.push(node);
}
const result: T[] = [];
while (queue.length > 0) {
const node = queue.shift()!;
result.push(node);
for (const dep of this.adjacency.get(node) ?? []) {
const newDegree = inDegree.get(dep)! - 1;
inDegree.set(dep, newDegree);
if (newDegree === 0) queue.push(dep);
}
}
if (result.length !== this.adjacency.size) {
throw new Error("检测到循环依赖");
}
return result;
}
}

Kahn 算法的核心思路:每次取出入度为 0 的节点,将其后继节点的入度减 1,重复直到所有节点处理完毕。如果最终结果长度不等于节点总数,说明存在循环——循环中的节点入度永远降不到 0。

六、生产验证#

  • Cargo(Rust 包管理器)dep_cache.rs#L143-L175 中的 RegistryQueryer 管理 Rust 包的依赖解析图,依赖形成通过回溯解析的 DAG,build_deps 产生每个候选包激活的依赖集合。
  • pnpmgraph-sequencer#L22-L125 中的 graphSequencer 按工作区包的相互依赖关系进行拓扑排序并检测循环,被 pnpm -r 递归命令使用。
  • Terraform — 资源依赖图实现并行 apply,依赖关系从显式引用和隐式依赖中自动推导。

七、小结#

何时使用:

  • 构建系统——依赖先于被依赖者编译(Make、Bazel、Cargo)
  • 包管理器——工作区包的安装/构建顺序(pnpm、npm、yarn)
  • 任务调度——带前置条件的作业编排(Airflow、CI/CD 流水线)
  • 数据库迁移——按依赖顺序应用 schema 变更

何时不用:

  • 设计上存在循环依赖——使用事件驱动或惰性求值等不同模型
  • 任务完全独立——简单列表即可,无需图结构
  • 依赖在运行时频繁变化——增量方法更合适
  • 超大规模图——Kahn 算法天然串行,需要并行改造

八、参考资料#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

依赖图(Dependency Graph)
https://blog.souloss.com/posts/programming/system-patterns/system-patterns-dependency-graph/
作者
Tsukimi
发布于
2026-06-13
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时