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

简单工厂模式(Simple Factory)不在 GoF 的 23 种设计模式之列,但它是很多人第一个真正用到的“模式”。它的意图只有一句话:把“该创建哪种对象”的判断逻辑,从调用方剥离出来,集中到一个工厂函数里。

理解这句话之前,先看它解决的是什么问题。

问题:创建逻辑散落在调用方

假设你在写一个多模型推理路由,调用方需要根据配置决定用哪个 LLM 客户端:

# 没有工厂的写法 —— 判断逻辑散落在各处
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 就需要改三处。这是简单工厂要处理的核心痛点。

实现:一个函数集中创建决策

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}")

调用方只需要:

client = create_llm_client(provider=settings.llm_provider, config=settings.dict())
response = client.complete(prompt)

provider 是什么、需要哪些初始化参数——调用方一概不知,也不需要知道。

工厂在 AI Infra 里的实际落点

简单工厂在 Agent 基础设施层有几个高频场景:

Memory Backend 路由:Agent 的记忆存储可以是 Redis、向量数据库、本地文件,运行时由配置决定:

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 函数)需要不同的执行器:

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):

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

设计模式Python设计模式创建型模式简单工厂
← 所有文章