设计模式(01)-简单工厂模式的 Python 实现与工程边界

简单工厂模式(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:
# 调用 Anthropic API
...


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:
# 调用 OpenAI API
...


class LocalVLLMClient:
def __init__(self, base_url: str) -> None:
self._base_url = base_url

def complete(self, prompt: str) -> str:
# 调用本地 vLLM 服务
...


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 链,工厂是个合适的清理手段;如果没有这个问题,引入工厂只是增加间接层。


设计模式(01)-简单工厂模式的 Python 实现与工程边界
https://www.krli.org/2018/04/03/设计模式-01-简单工厂模式-Python实现/
作者
李科燃
发布于
2018年4月3日
许可协议