## Keyboard shortcuts

Press `←` or `→` to navigate between chapters

Press `S` or `/` to search in the book

Press `?` to show this help

Press `Esc` to hide this help

# 第 24 章：设计模式提炼

> “设计模式不是教条，而是对反复出现的问题的最佳回答的提炼。在 Claude Code 中，这些模式被重新演绎为 Agent 时代的变体。”

本章从 Claude Code 的源码中提炼出最具特色的设计模式。这些模式不是照搬 GoF 经典，而是在 AI Agent 系统的独特约束下演化出的新形态。

## 24.1 依赖注入 —— ToolUseContext

### 24.1.1 模式描述

`ToolUseContext` 是 Claude Code 中最核心的依赖注入容器。每个工具调用都接收一个 ToolUseContext 对象，其中包含了工具执行所需的全部环境信息：

    // src/Tool.ts
    export type ToolUseContext = {
      options: {
        mainLoopModel: string
        maxThinkingTokens: number | undefined
      }
      agentId: AgentId
      readFileState: FileStateCache
      writeFileState: FileStateCache
      tools: Tool[]
      mcpClients: MCPServerConnection[]
      canUseTool: CanUseToolFn
      // ... 更多上下文
    }

### 24.1.2 为什么不用传统 DI 框架

Claude Code 没有使用 InversifyJS 或 tsyringe 等 DI 框架。ToolUseContext 是一个显式的参数对象（Parameter Object），而非容器管理的服务。原因在于：

1. 生命周期明确 —— Context 在每个查询轮次中创建，随轮次结束销毁
2. 类型安全 —— TypeScript 的结构类型系统已经提供了足够的接口抽象
3. 可测试性 —— 在测试中构造 Context 对象比配置 DI 容器更直观

    graph TD
        QueryLoop[查询循环] --> |每轮创建| Context[ToolUseContext]
        Context --> BashTool[BashTool.call context]
        Context --> EditTool[FileEditTool.call context]
        Context --> ReadTool[FileReadTool.call context]
        Context --> AgentTool[AgentTool.call context]

        Context --> |包含| Model[model 配置]
        Context --> |包含| Cache[文件缓存]
        Context --> |包含| Perms[权限函数]

### 24.1.3 Context 传播

工具调用时，Context 被向下传播。子 Agent 接收父 Agent 的 Context 变体：

    // AgentTool 创建子 Context
    const childContext: ToolUseContext = {
      ...parentContext,
      agentId: childAgentId,
      readFileState: createFileStateCacheWithSizeLimit(READ_FILE_STATE_CACHE_SIZE),
      // 子 Agent 获得独立的文件缓存但共享工具集
    }

## 24.2 失败关闭 —— 安全默认值

### 24.2.1 模式描述

在安全关键系统中，“失败关闭“意味着当系统无法确定正确的行为时，选择更安全（更限制性的）默认值。Claude Code 在权限系统中严格遵循这一原则。

### 24.2.2 权限默认值

    // src/state/AppStateStore.ts
    export function getDefaultAppState(): AppState {
      return {
        toolPermissionContext: {
          ...getEmptyToolPermissionContext(),
          mode: initialMode,  // 默认为 'default'（需要确认）
        },
        // ...
      }
    }

默认权限模式是 `'default'` —— 需要用户确认每个工具操作。用户必须显式选择 `'auto'` 模式才能跳过确认。

### 24.2.3 Bypass 权限的保护

    // src/state/AppState.tsx
    useEffect(() => {
      const { toolPermissionContext } = store.getState()
      if (toolPermissionContext.isBypassPermissionsModeAvailable
          && isBypassPermissionsModeDisabled()) {
        logForDebugging("Disabling bypass permissions mode on mount")
        store.setState(prev => ({
          ...prev,
          toolPermissionContext: createDisabledBypassPermissionsContext(
            prev.toolPermissionContext
          ),
        }))
      }
    }, [])

即使 bypass 模式在本地可用，远程设置（可能在挂载后才加载）仍可以禁用它。这是“失败关闭“在时序问题上的应用 —— 在安全信号到达前，保持最安全的状态。

### 24.2.4 断路器也是失败关闭

    // 连续失败 3 次后停止重试
    if (tracking?.consecutiveFailures >= MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES) {
      return { wasCompacted: false }
    }

压缩的断路器也遵循失败关闭 —— 当压缩反复失败时，停止尝试而非无限重试。“不做事“比“做错事“更安全。

## 24.3 Generator 流水线 —— AsyncGenerator 组合

### 24.3.1 模式描述

Claude Code 的查询引擎使用 AsyncGenerator 模式处理流式响应。每个处理阶段是一个 generator，通过组合形成流水线。

### 24.3.2 流式处理链

    // 概念模型
    async function* queryModelWithStreaming(
      messages: Message[],
      systemPrompt: string,
      tools: Tool[],
    ): AsyncGenerator<StreamEvent> {
      const stream = await api.messages.stream(/* ... */)

      for await (const event of stream) {
        // 每个事件被逐一 yield
        yield transformEvent(event)
      }
    }

    // REPL 消费流
    for await (const event of queryModelWithStreaming(messages, system, tools)) {
      if (event.type === 'content_block_delta') {
        // 增量更新 UI
        updateMessages(event)
      } else if (event.type === 'tool_use') {
        // 触发工具调用
        yield* handleToolUse(event)  // yield* 委托到子 generator
      }
    }

### 24.3.3 流水线优势

    graph LR
        API[API Stream] --> |event| Transform[Transform<br/>事件转换]
        Transform --> |event| Filter[Filter<br/>过滤噪音]
        Filter --> |event| Accumulate[Accumulate<br/>累积内容块]
        Accumulate --> |message| ToolDispatch[ToolDispatch<br/>工具调度]
        ToolDispatch --> |result| UI[UI Update<br/>界面更新]

AsyncGenerator 组合的优势在于：

1. 背压 —— 下游处理慢时自动暂停上游
2. 懒求值 —— 只在需要时拉取下一个事件
3. 取消 —— 可以随时终止流水线（用户按 Escape）
4. 组合性 —— 每个阶段独立可测试

## 24.4 观察者模式 —— Hook 系统

### 24.4.1 模式描述

Claude Code 的 Hook 系统是观察者模式的一种变体。Hook 允许用户在关键事件（会话开始、压缩前后、工具调用前后）注入自定义逻辑。

### 24.4.2 会话钩子

    // Hook 类型
    export type SessionHooksState = Map<string, HookState>

    // Hook 执行点
    import { processSessionStartHooks } from '../utils/sessionStart.js'
    import { executeSessionEndHooks } from '../utils/hooks.js'
    import { executePreCompactHooks, executePostCompactHooks } from '../utils/hooks.js'

### 24.4.3 onChange 作为观察者

Store 的 `onChange` 回调也是一种观察者实现：

    const store = createStore(initialState, onChangeAppState)

    // onChangeAppState 观察所有状态变化
    function onChangeAppState({ newState, oldState }) {
      // 权限模式变化 → 通知 CCR
      // 模型变化 → 更新 settings
      // 详细模式变化 → 更新 globalConfig
      // 设置变化 → 清除认证缓存
    }

## 24.5 策略模式 —— 权限分类器

### 24.5.1 模式描述

权限分类器使用策略模式 —— 根据当前权限模式选择不同的决策策略。

### 24.5.2 权限模式策略

    graph TD
        Request[工具调用请求] --> Mode{权限模式?}
        Mode -->|default| DefaultStrategy[Default 策略<br/>检查允许列表<br/>否则需要确认]
        Mode -->|plan| PlanStrategy[Plan 策略<br/>仅规划不执行<br/>全部拒绝]
        Mode -->|auto| AutoStrategy[Auto 策略<br/>分类器自动决策<br/>高风险仍需确认]

        DefaultStrategy --> |需要确认| UI[显示权限对话框]
        DefaultStrategy --> |已授权| Execute[执行]
        PlanStrategy --> Reject[拒绝执行]
        AutoStrategy --> |低风险| Execute
        AutoStrategy --> |高风险| UI

### 24.5.3 可扩展性

新的权限模式可以通过添加新的策略实现来扩展，无需修改现有代码。`PermissionMode` 联合类型确保了所有策略都被穷尽处理：

    export type PermissionMode =
      | 'default'
      | 'plan'
      | 'auto'
      | 'bubble'
      | 'ungated_auto'
      // ...

## 24.6 工厂模式 —— buildTool, createStore

### 24.6.1 Store 工厂

`createStore` 是最纯粹的工厂函数 —— 接收配置（初始状态、onChange），返回一个完整的 Store 实例：

    export function createStore<T>(
      initialState: T,
      onChange?: OnChange<T>,
    ): Store<T> {
      let state = initialState
      const listeners = new Set<Listener>()
      return { getState, setState, subscribe }
    }

### 24.6.2 缓存工厂

    export function createFileStateCacheWithSizeLimit(
      maxEntries: number,
      maxSizeBytes: number = DEFAULT_MAX_CACHE_SIZE_BYTES,
    ): FileStateCache {
      return new FileStateCache(maxEntries, maxSizeBytes)
    }

### 24.6.3 默认状态工厂

    export function getDefaultAppState(): AppState {
      // 60+ 个字段的默认值构建
      return { ... }
    }

工厂模式在 Claude Code 中的应用特点是：工厂函数通常是纯函数（或接近纯函数），不依赖全局状态。这使得它们在测试中特别容易使用 —— 每次调用都产生一个独立的实例。

### 24.6.4 CompactionResult 构建器

    export function buildPostCompactMessages(result: CompactionResult): Message[] {
      return [
        result.boundaryMarker,
        ...result.summaryMessages,
        ...(result.messagesToKeep ?? []),
        ...result.attachments,
        ...result.hookResults,
      ]
    }

这是一个构建器（Builder）变体 —— 从结构化的中间表示构建最终的消息列表。顺序封装在函数内部，调用者无需了解消息组装规则。

## 24.7 双通道模式 —— 隔离与共享的平衡

### 24.7.1 模式描述

当一个子系统需要同时支持“默认隔离“和“显式共享“两种语义时，Claude Code 使用双通道设计。最典型的例子是 `ToolUseContext` 中的 `setAppState` 与 `setAppStateForTasks`（参见第 7 章 §7.3.3）。

### 24.7.2 隔离通道 vs 共享通道

    graph TD
        Parent[父 Agent] --> |setAppState| ParentStore[父 Store]
        Parent --> |setAppStateForTasks| RootStore[根 Store]

        SubAgent[子 Agent] --> |setAppState| NoOp["no-op<br/>(隔离：不影响父)"]
        SubAgent --> |setAppStateForTasks| RootStore

        SubAgent2[孙 Agent] --> |setAppState| NoOp2["no-op<br/>(隔离)"]
        SubAgent2 --> |setAppStateForTasks| RootStore

        style NoOp fill:#fdd
        style NoOp2 fill:#fdd
        style RootStore fill:#dfd

- 隔离通道（`setAppState`）：子 Agent 的状态修改不泄漏到父 Agent，防止多层嵌套 Agent 之间的状态污染
- 共享通道（`setAppStateForTasks`）：穿透到根 Store，用于全局基础设施（后台任务注册、会话钩子）

### 24.7.3 模式应用矩阵

场景 | 通道 | 原因
--- | --- | ---
普通状态更新 | 隔离 | 防止子 Agent 副作用泄漏
后台任务注册 | 共享 | 任务需要在 Agent 结束后存活
会话钩子 | 共享 | 钩子是全局基础设施
文件缓存 | 隔离 | 每个 Agent 独立缓存，避免缓存膨胀
权限追踪 | 隔离 | 拒绝计数不应跨 Agent 累计

## 24.8 编译时多态 —— feature() 宏

### 24.8.1 模式描述

传统的策略模式在运行时通过接口和多态实现分支。Claude Code 使用 `feature()` 宏实现编译时多态 —— 不同构建配置产生不同的代码，运行时无分支开销。

### 24.8.2 与运行时策略的对比

    // 运行时策略（传统方式）
    function getVoiceProvider() {
      if (config.voiceEnabled) {        // 运行时检查
        return new VoiceProvider()       // 代码始终在包中
      }
      return new NoopProvider()
    }

    // 编译时多态（Claude Code 方式）
    const VoiceProvider = feature('VOICE_MODE')
      ? require('../context/voice.js').VoiceProvider   // 内部构建：包含
      : ({ children }) => children                      // 外部构建：DCE 移除 voice.js

编译时多态的优势：

1. 零运行时开销 —— 无条件分支
2. 安全保证 —— 内部代码物理上不存在于外部产物中
3. 包大小优化 —— 未启用的模块及其依赖树被完全移除

### 24.8.3 已知的 Feature Flag 清单

从源码中提取的完整 feature flag 列表：

Feature Flag | 功能 | 外部构建
--- | --- | ---
`VOICE_MODE` | 语音输入 | 移除
`COORDINATOR_MODE` | 多 Agent 协调器 | 移除
`PROACTIVE` | 后台主动 Agent | 移除
`KAIROS` | 助手/通道模式 | 移除
`FORK_SUBAGENT` | Fork 子 Agent | 移除
`TRANSCRIPT_CLASSIFIER` | 自动权限模式 | 移除
`BASH_CLASSIFIER` | Bash 命令分类器 | 移除
`HISTORY_SNIP` | 上下文片段压缩 | 移除
`VERIFICATION_AGENT` | 验证 Agent | 移除
`BRIDGE_MODE` | IDE 桥接 | 移除

## 24.9 反模式警示

### 24.7.1 巨型组件

REPL.tsx 的 5000 行是一个需要关注的信号。虽然 React Compiler 的自动 memoization 缓解了性能问题，但维护性仍然是挑战。团队通过将逻辑提取到 hooks（`useTasksV2WithCollapseEffect`、`useReplBridge` 等）来逐步减小主组件的体积。

### 24.7.2 全局可变状态

`src/bootstrap/state.ts` 文件头的警告说明了团队的态度：

    // DO NOT ADD MORE STATE HERE - BE JUDICIOUS WITH GLOBAL STATE

全局状态虽然方便，但增加了不可见的耦合。团队正在将状态逐步迁移到 AppState Store 中，通过 `onChange` 机制统一管理副作用。

## 本章小结

Claude Code 的设计模式展现了一种实用主义的工程审美。ToolUseContext 的显式依赖注入避免了框架的复杂性，`feature()` 宏的策略模式实现了编译时多态，AsyncGenerator 流水线为流式处理提供了优雅的抽象。

最值得注意的是“失败关闭“原则的无处不在 —— 从默认权限模式到断路器，从 bypass 权限的动态禁用到 identity guard 的并发保护。在一个运行着实际代码的 AI Agent 中，安全不是可选项，而是设计的起点。