跳转至

Dependency Injection Analysis

重要性:⭐⭐⭐ 目标读者:架构师 / 高级开发者 关联analysis/module-dependencies.md


1. 概览

本文档分析 Claude Code 的依赖注入(DI)模式

5 主题: - DI 模式 - 实际实现 - 优点 - 缺点 - 改进方向


2. 5 大 DI 模式

2.1 构造注入

class Engine {
  constructor(private client: Anthropic) {}
}

最常见

2.2 Setter 注入

engine.setClient(client)

修改

2.3 接口注入

interface Client {
  call(req): Promise<res>
}
class Engine {
  constructor(private client: Client) {}
}

抽象

2.4 服务定位

const client = ServiceLocator.get<Anthropic>('anthropic')

查找

2.5 依赖倒置

// 高层不依赖低层,都依赖抽象
class Engine {
  constructor(private client: Client) {}  // 抽象接口
}

DIP


3. Claude Code 的 DI 模式

3.1 实际模式:手工注入 + 全局单例

// 入口
const main = async () => {
  const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })
  const tools = getTools()
  // ...
  const engine = new QueryEngine({ client, tools, ... })
}

手工 + 单例

3.2 ToolUseContext 传递

// 工具通过 context 接收
async function Read(input, context: ToolUseContext) {
  // context 含:client, store, appState, ...
}

context 模式

3.3 Hooks 接收服务

function useClaudeApi() {
  // 通过 hook 获取 client
}

hooks 模式

3.4 zustand-style 状态

const verbose = useAppState(s => s.verbose)

subscribe

3.5 feature gate 替代

const x = feature('X') ? require('./X.js') : null

模块级


4. 4 个 DI 关键文件

4.1 bootstrap/state.js

// 全局 set 函数
setIsRemoteMode(true)
setAdditionalDirectoriesForClaudeMd(...)

setter

4.2 state/AppStateStore.ts

// zustand-style
export const useAppState = create<State>(...)

store

4.3 Tool.ts

// 工具接口 + context
interface Tool {
  call(input, context: ToolUseContext)
}

interface

4.4 entrypoints/init.js

// 初始化
init()

init


5. 5 个优点

5.1 简单

手工注入, 框架。

5.2 显式

依赖显式 在构造。

5.3 可测

可注入 mock。

5.4 无魔法

不依赖框架。

5.5 TS 友好

类型安全。


6. 5 个缺点

6.1 重复

// 多个文件重复传 client
new Engine({ client })
new Tool({ client })

重复

6.2 深度传递

// context 一层传一层
A -> B -> C -> D
// D 想用 client,得通过 B C 传

6.3 全局状态

// state/AppStateStore 是全局
// 任何地方都能 useAppState

global

6.4 测试 mock 复杂

// Mock 整个 context

mock

6.5 重构风险

// 改 context 形状 → 多文件

refactor


7. 实际代码例子

7.1 ToolUseContext

interface ToolUseContext {
  // 5 大类
  agentId?: string
  options: ToolUseContextOptions
  abortController: AbortController
  getAppState: () => AppState
  setAppState: (updater) => void
  // ... ~30 字段
}

async function Read(
  input: { file_path: string },
  context: ToolUseContext,
): Promise<ToolResult> {
  const cwd = context.options.cwd
  const appState = context.getAppState()
  // ...
}

~30 字段 context

7.2 单例 + setter

// state/AppStateStore.ts
let _store: AppState

export function getAppState(): AppState {
  return _store
}

export function setAppState(updater: (prev: AppState) => AppState): void {
  _store = updater(_store)
}

单例 + setter

7.3 Hooks 服务定位

// hooks/useClaudeApi.ts
export function useClaudeApi() {
  return useMemo(() => createClaudeApi(), [])
}

hook

7.4 feature 模块注入

// 服务可选
const computerUseWrapper = feature('CHICAGO_MCP') 
  ? require('...') : null

feature


8. 5 个改进方向

8.1 引入 DI 容器

// tsyringe / inversify
import { container } from 'tsyringe'

container.register('Anthropic', { useValue: client })

容器

8.2 抽象接口

interface IClaudeApi {
  call(req): Promise<res>
}
// 实现可换
class AnthropicImpl implements IClaudeApi { ... }
class MockImpl implements IClaudeApi { ... }

interface

8.3 Context 简化

// 30 字段 → 5 大类
interface Context {
  auth: AuthContext
  state: StateContext
  tools: ToolsContext
  // ...
}

分组

8.4 Provider 模式

<Provider value={client}>
  <Engine />
</Provider>

provider

8.5 工厂模式

const engine = EngineFactory.create({ client, tools })

factory


9. 5 个测试考虑

9.1 Mock client

const mockClient = {
  call: jest.fn().mockResolvedValue({ ... })
}
const engine = new QueryEngine({ client: mockClient, ... })

mock

9.2 In-memory state

const state = createTestState()

test state

9.3 工具 stub

const stubTool = { call: () => 'result' }

stub

9.4 集成测试

// 用真实 client + 真实 API

integration

9.5 E2E

// 跑整个流程

E2E


10. 5 个常见问题

10.1 循环依赖

A -> B -> A

解决:lazy require / getter。

10.2 大 context

// 30 字段太多

解决:分组 + interface。

10.3 难以 mock

// 内部 import 多

解决:依赖注入。

10.4 测试慢

// 启动慢

解决:mock + 简化。

10.5 重构难

// 改一个地方多文件

解决:interface 隔离。


11. 5 个最佳实践

11.1 显式注入

// ❌ import global
// ✅ 构造注入

显式

11.2 小 context

// 只传需要的

11.3 抽象接口

// interface > 具体类

interface

11.4 测试友好

// 可 mock

test

11.5 文档化

// JSDoc

docs


12. 与其他模式对比

模式 优点 缺点
手工注入 简单 重复
DI 容器 集中 魔法
Service Locator 易用 全局
Provider 灵活 嵌套
手工 + 单例 显式 测试难

5 模式


13. 关键 trade-off

简单  ↔  灵活
显式  ↔  集中
测试  ↔  性能
类型  ↔  抽象

4 维 trade-off


14. 5 个 insight

14.1 Claude Code 选"手工 + 单例"

理由:简单、显式、TS 友好。

14.2 ToolUseContext 30 字段

多但合理——业务丰富。

14.3 feature gate 是 DI

变体——编译时注入。

14.4 没有 DI 容器

故意——避免魔法。

14.5 测试 mock 复杂

代价——手动 mock。


15. 总结

Dependency Injection Analysis = 5 主题 + 5 模式 + 5 优缺点

核心: - 手工 + 单例 - ToolUseContext 30 字段 - feature gate 模块注入 - 5 改进方向

下一步: - 看 module-dependencies.md - 考虑 DI 容器 - 抽象接口