Skip to content

Latest commit

 

History

History
263 lines (182 loc) · 9.44 KB

File metadata and controls

263 lines (182 loc) · 9.44 KB

第28章:架构之美——设计模式总结

回顾这段旅程

我们已经走过了 27 章的旅程,从程序入口到查询引擎,从工具系统到安全防线,从 MCP 协议到多智能体协作。现在是时候"站在山顶上"回头看看整个风景了。

在这一章中,我们将提炼出 Claude Code 源码中反复出现的设计模式——这些模式不仅适用于 Claude Code,也适用于你将来写的任何大型程序。

模式一:统一接口(Uniform Interface)

哪里用到了? 所有工具都遵循 Tool<Input, Output, Progress> 接口。

// 40+ 个工具,同一个接口
type Tool = {
  name: string
  call(input): Promise<ToolResult>
  inputSchema: ZodSchema
  checkPermissions(input): Promise<PermissionResult>
  // ...
}

为什么这样设计?

想象如果每个工具有自己的接口:

// 混乱的设计
bash.execute(command)           // Bash 用 execute
fileReader.open(path)           // FileRead 用 open
grep.search(pattern, directory) // Grep 用 search

调用者需要知道每个工具的特殊方法。每加一个工具,调用代码就要改一次。

统一接口意味着:

// 整洁的设计
for (const tool of tools) {
  const result = await tool.call(input)  // 所有工具用同一个方法
}

调用者不需要知道具体是哪个工具——它们都长一样。这就是多态的力量。

生活比喻: 所有电器都用相同的插头标准。你不需要为台灯、电脑、冰箱分别安装不同的插座。

模式二:分层架构(Layered Architecture)

哪里用到了? 整个项目的目录组织。

UI 层      → components/
命令层     → commands/
查询层     → query.ts
工具层     → tools/
服务层     → services/
状态层     → state/
基础设施层 → utils/

为什么这样设计?

分层让每一层只关心自己的事情:

  • UI 层不知道(也不需要知道)API 是怎么调用的
  • 工具层不知道(也不需要知道)界面是怎么画的
  • 每一层通过明确的接口与相邻层通信

好处:如果你想把终端界面换成网页界面,只需要改 UI 层,其他六层不用动。

生活比喻: 一栋大楼的每层有不同的功能——地下室是停车场,一楼是大厅,二楼是办公室。改装一楼的大厅不影响二楼的办公室。

模式三:故障安全(Fail-Safe Defaults)

哪里用到了? 工具的默认安全属性。

const TOOL_DEFAULTS = {
  isConcurrencySafe: () => false,  // 默认不安全
  isReadOnly: () => false,          // 默认可写
  isDestructive: () => false,       // 默认不危险
}

为什么这样设计?

如果默认是"安全的"(isConcurrencySafe: true),那么一个忘了设置这个属性的新工具可能被错误地并行执行,导致数据损坏。

如果默认是"不安全的"(isConcurrencySafe: false),忘了设置的工具只会被串行执行——稍慢,但不会出错。

原则:当你不确定时,选择安全的选项。

生活比喻: 电梯在断电时会自动下到一楼并打开门(安全状态),而不是停在半空中关着门。

模式四:事件驱动(Event-Driven)

哪里用到了? Hook 系统、流式响应处理、状态更新。

// 事件发生 → 触发处理函数
hooks.on("PreToolUse", (event) => { ... })
hooks.on("PostToolUse", (event) => { ... })
hooks.on("SessionEnd", (event) => { ... })

为什么这样设计?

事件驱动让系统高度可扩展——你可以在任何事件上"挂载"新的行为,而不需要修改核心代码。

想加一个"每次文件修改后自动格式化"的功能?不用改文件编辑的代码,只需要在 PostToolUse 事件上挂一个 Hook。

生活比喻: 订阅制——你订阅了"新包裹到达"的通知,有新包裹时你会收到通知,而不需要每小时去快递站检查一次。

模式五:生产者-消费者(Producer-Consumer)

哪里用到了? 流式响应处理。

API(生产者)→→→ 事件流 →→→ UI(消费者)
      │                         │
  一个字一个字地                 收到一个字
  生产文本                       就显示一个字

为什么这样设计?

生产者和消费者速度不同——API 生成文本的速度和终端渲染的速度不一样。通过事件流作为"缓冲区",两边可以以各自的速度工作。

生活比喻: 餐厅的厨房和服务员。厨房(生产者)做好一道菜就放到出菜口,服务员(消费者)从出菜口取菜送给客人。厨房不需要等服务员回来才做下一道菜。

模式六:策略模式(Strategy Pattern)

哪里用到了? 权限决策、沙箱选择、重试策略。

// 不同的权限模式是不同的"策略"
if (mode === "default") {
  strategy = new DefaultPermissionStrategy()
} else if (mode === "auto") {
  strategy = new AutoPermissionStrategy()
} else if (mode === "bypass") {
  strategy = new BypassPermissionStrategy()
}

// 使用策略
const decision = await strategy.checkPermission(tool, input)

为什么这样设计?

策略模式让你可以在运行时切换算法。用户选择不同的权限模式,本质上是选择了不同的权限检查策略。

生活比喻: GPS 导航让你选择"最快路线"、"最短路线"或"避开高速"——不同的路由策略,同一个目的地。

模式七:纵深防御(Defense in Depth)

哪里用到了? 整个安全系统。

第一层:AI 的自我约束
第二层:工具级验证
第三层:权限规则
第四层:AI 分类器
第五层:用户确认
第六层:沙箱隔离

为什么这样设计?

任何单一的安全措施都可能被绕过。多层防御确保即使某一层失败,其他层仍然能保护系统。

生活比喻: 银行的安全系统——大门、摄像头、保安、保险柜、密码锁、报警器。小偷可能骗过其中一个,但不太可能骗过所有。

模式八:缓存策略(Caching Strategy)

哪里用到了? 提示缓存、文件缓存、工具 schema 缓存。

提示缓存:系统提示词不变 → 重用缓存 → 节省 90% 费用
文件缓存:LRU 策略 → 最近读过的文件不重复读取
Schema 缓存:工具定义不变 → 不重复生成 JSON Schema

核心原则: 如果一个计算的输入没有变化,就不需要重新计算。

生活比喻: 你每天走同一条路上学,不需要每次都打开地图导航。记住一次路线,以后直接走。

模式九:渐进增强(Progressive Enhancement)

哪里用到了? 功能开关、条件加载。

// 基础功能总是可用
const tools = [Bash, FileRead, FileEdit]

// 高级功能根据条件添加
if (feature('VOICE_MODE')) tools.push(VoiceTool)
if (feature('WEB_BROWSER')) tools.push(BrowserTool)
if (feature('LSP_INTEGRATION')) tools.push(LSPTool)

为什么这样设计?

不是所有用户都需要所有功能。基础功能保证可用,高级功能按需启用。这样:

  • 新功能不会影响稳定性
  • 启动速度不会因为功能增加而变慢
  • 不同用户看到不同的功能集

模式十:关注点分离(Separation of Concerns)

这是贯穿整个项目的核心原则:

命令解析 vs 命令执行      → 分开
工具定义 vs 工具权限      → 分开
消息格式 vs 消息渲染      → 分开
状态存储 vs 状态使用      → 分开
安全规则 vs 安全执行      → 分开

每个模块只负责一件事。这让代码更容易理解、测试和修改。

本章小结

模式 核心思想 Claude Code 中的例子
统一接口 所有组件遵循相同的接口 40+ 工具同一个 Tool 类型
分层架构 按职责分层,层间有明确接口 UI → 命令 → 查询 → 工具 → 服务
故障安全 默认选择安全的选项 工具默认"不安全"
事件驱动 通过事件解耦组件 Hook 系统
生产者-消费者 异步的数据流 流式 API 响应
策略模式 运行时切换算法 权限模式选择
纵深防御 多层安全防护 六层安全系统
缓存策略 避免重复计算 提示缓存、LRU 缓存
渐进增强 基础功能保底,高级功能按需 功能开关系统
关注点分离 每个模块只做一件事 整个项目的组织方式

这些模式不是 Claude Code 独创的——它们是软件工程几十年积累的智慧。掌握这些模式,你就拥有了构建任何大型系统的基础。

模式的组合之美

在实际的系统中,模式很少单独出现——它们互相组合,形成更强大的设计。

比如 Claude Code 的工具执行流程就组合了多个模式:

统一接口 → 所有工具用同一个 call() 方法
  + 策略模式 → 不同的权限检查策略
  + 事件驱动 → Hook 在执行前后触发
  + 纵深防御 → 多层安全检查
  + 缓存策略 → 工具 schema 缓存
  = 一个既灵活又安全又高效的工具执行系统

设计模式就像乐高积木——每块积木很简单,但组合起来可以搭建出复杂精美的作品。

下一章,我们将从源码中提炼工程实践的启示。


本书由 everettjf 使用 Claude Code 分析泄露源码编写 | 保留出处即可自由转载