简单工厂模式(Simple Factory)不在 GoF 的 23 种设计模式之列,但它是很多人第一个真正用到的”模式”。它的意图只有一句话:把”该创建哪种对象”的判断逻辑,从调用方剥离出来,集中到一个工厂函数里。
理解这句话之前,先看它解决的是什么问题。
问题:创建逻辑散落在调用方
假设你在写一个多模型推理路由,调用方需要根据配置决定用哪个 LLM 客户端:
1 2 3 4 5 6 7
| if provider == "anthropic": client = AnthropicClient(api_key=config.anthropic_key) elif provider == "openai": client = OpenAIClient(api_key=config.openai_key, model=config.model) elif provider == "local": client = LocalVLLMClient(base_url=config.vllm_url)
|
当这段逻辑出现在三个地方,新增一个 provider 就需要改三处。这是简单工厂要处理的核心痛点。
实现:一个函数集中创建决策
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| from typing import Protocol
class LLMClient(Protocol): def complete(self, prompt: str) -> str: ...
class AnthropicClient: def __init__(self, api_key: str) -> None: self._api_key = api_key
def complete(self, prompt: str) -> str: ...
class OpenAIClient: def __init__(self, api_key: str, model: str) -> None: self._api_key = api_key self._model = model
def complete(self, prompt: str) -> str: ...
class LocalVLLMClient: def __init__(self, base_url: str) -> None: self._base_url = base_url
def complete(self, prompt: str) -> str: ...
def create_llm_client(provider: str, config: dict) -> LLMClient: """简单工厂:集中所有 provider 的创建逻辑。""" if provider == "anthropic": return AnthropicClient(api_key=config["anthropic_key"]) if provider == "openai": return OpenAIClient(api_key=config["openai_key"], model=config["model"]) if provider == "local": return LocalVLLMClient(base_url=config["vllm_url"]) raise ValueError(f"Unknown provider: {provider}")
|
调用方只需要:
1 2
| client = create_llm_client(provider=settings.llm_provider, config=settings.dict()) response = client.complete(prompt)
|
provider 是什么、需要哪些初始化参数——调用方一概不知,也不需要知道。
工厂在 AI Infra 里的实际落点
简单工厂在 Agent 基础设施层有几个高频场景:
Memory Backend 路由:Agent 的记忆存储可以是 Redis、向量数据库、本地文件,运行时由配置决定:
1 2 3 4 5 6 7 8
| def create_memory_backend(backend_type: str, config: dict) -> MemoryBackend: if backend_type == "redis": return RedisMemoryBackend(url=config["redis_url"]) if backend_type == "chroma": return ChromaMemoryBackend(collection=config["collection"]) if backend_type == "in_memory": return InMemoryBackend() raise ValueError(f"Unsupported backend: {backend_type}")
|
Tool Executor 分发:不同类型的 tool call(HTTP、subprocess、Python 函数)需要不同的执行器:
1 2 3 4 5 6 7 8
| def create_tool_executor(tool_spec: ToolSpec) -> ToolExecutor: if tool_spec.type == "http": return HTTPToolExecutor(base_url=tool_spec.url) if tool_spec.type == "function": return FunctionToolExecutor(fn=tool_spec.fn) if tool_spec.type == "subprocess": return SubprocessToolExecutor(command=tool_spec.command) raise ValueError(f"Unknown tool type: {tool_spec.type}")
|
两者的共同特征:创建时需要不同的初始化参数,但创建完成后调用方使用统一接口。
工厂方法 vs 简单工厂
常见的混淆:简单工厂是一个函数(或静态方法),工厂方法模式是一个类继承结构。
|
简单工厂 |
工厂方法模式 |
| 结构 |
单个函数/静态方法 |
抽象基类 + 子类重写 |
| 新增类型 |
修改工厂函数 |
新增子类,不改现有代码 |
| 适用 |
类型固定、变化少 |
类型开放、可扩展 |
如果你的 provider 列表会频繁扩展,或者需要让外部插件注入新类型,简单工厂会成为瓶颈——每次新增都要改工厂函数,违反开闭原则。这时应该升级为注册表模式(Registry):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| _registry: dict[str, type] = {}
def register_client(name: str): def decorator(cls): _registry[name] = cls return cls return decorator
@register_client("anthropic") class AnthropicClient: ...
@register_client("openai") class OpenAIClient: ...
def create_llm_client(provider: str, **kwargs) -> LLMClient: if provider not in _registry: raise ValueError(f"Unknown provider: {provider}") return _registry[provider](**kwargs)
|
注册表允许在不修改工厂函数的前提下扩展新类型,插件化架构里更常见。
什么时候不该用
简单工厂的本质是一个知道所有具体类型的中心节点。这意味着:
- 如果具体类型只有一两个,直接在调用方写
if 反而更清晰,工厂是多余的抽象。
- 如果工厂函数需要处理的类型超过五六种,或者初始化逻辑复杂度各异,考虑拆分成多个专门的工厂或用注册表。
- 单元测试时,工厂中心化意味着 mock 也要经过工厂,注意测试边界的设计。
简单工厂的价值是把”选择哪种实现”的决策收拢到一处。如果你的代码里已经有这种散落的 if/elif 链,工厂是个合适的清理手段;如果没有这个问题,引入工厂只是增加间接层。