Mobile wallpaper 1
1701 字
9 分钟
笔记·设计模式:工厂方法与抽象工厂

引言·关于设计模式#

设计模式(Design Patterns)是在特定上下文中反复出现的软件设计问题的通用解法。它不是代码模板,而是可复用的设计思想与结构,用于提升系统的可维护性、可扩展性和可理解性。

从工程视角看,设计模式主要应对三类长期问题:

  1. 变化管理:如何在需求变化时,避免牵一发而动全身。
  2. 复杂度控制:如何拆分职责,避免对象之间形成隐式耦合。
  3. 协作与沟通:设计模式提供了一套共享的“设计语言”,降低团队沟通成本。

需要强调的是:设计模式不是目标,而是结果。它通常不是被“选中”的,而是在系统演化过程中自然浮现出来的。


设计模式的整体分类(GoF)#

在《设计模式:可复用面向对象软件的基础》中,GoF 将 23 种设计模式分为三类:

1. 创建型(Creational)#

关注:对象如何被创建

模式核心问题
Singleton全局唯一实例
Factory Method延迟具体类型决定
Abstract Factory产品族一致性
Builder复杂对象的逐步构建
Prototype通过复制创建对象

2. 结构型(Structural)#

关注:对象如何组合

模式核心问题
Adapter接口不兼容
Decorator功能叠加而不爆炸继承
Proxy访问控制 / 延迟加载
Facade简化复杂子系统
Composite树形结构统一处理
Bridge抽象与实现解耦
Flyweight减少对象数量

3. 行为型(Behavioral)#

关注:对象如何协作

模式核心问题
Strategy算法可替换
Observer状态变化通知
Command行为对象化
State状态驱动行为
Template Method流程固定,步骤可变
Chain of Responsibility请求传递
Mediator多对多解耦
Visitor操作与数据结构解耦
Memento状态保存与恢复
Interpreter规则解释

在日常工程中,我们早已在频繁使用这些模式:

  • 插件系统 → Strategy / Factory / Observer
  • Nonebot 扩展 → Template Method + Hook
  • 队列系统 → Command / Chain of Responsibility
  • 模型加载器 → Abstract Factory

只是我们通常是在工程层面使用它们,而不是在教科书层面命名它们


工厂方法(Factory Method)#

设计动机#

工厂方法要解决的核心问题是:

调用方不应该依赖具体类,只应该依赖“创建接口”。

典型的坏味道如下:

if t == "a":
obj = A()
elif t == "b":
obj = B()

问题并不在 if,而在于:

  • 调用方知道了所有具体类
  • 新增类型时,调用方必须被修改
  • 创建逻辑与业务逻辑发生了耦合

在这个例子中,真正会变化的并不是业务流程,而是:

“在某个条件下该创建哪一种对象”

工厂方法的作用,就是把这个变化轴从业务逻辑中抽离出来。


结构拆解#

工厂方法的最小结构由三部分组成:

  1. 产品抽象(接口 / 基类)
  2. 具体产品
  3. 创建方法(由实现者决定返回哪种产品)

Python 示例#

产品接口

from abc import ABC, abstractmethod
class Payment(ABC):
@abstractmethod
def pay(self, amount: float) -> None:
pass

具体产品

class AliPay(Payment):
def pay(self, amount: float) -> None:
print(f"AliPay: {amount}")
class WeChatPay(Payment):
def pay(self, amount: float) -> None:
print(f"WeChatPay: {amount}")

工厂方法接口与实现

class PaymentFactory(ABC):
@abstractmethod
def create_payment(self) -> Payment:
pass
class AliPayFactory(PaymentFactory):
def create_payment(self) -> Payment:
return AliPay()
class WeChatPayFactory(PaymentFactory):
def create_payment(self) -> Payment:
return WeChatPay()

使用方

def checkout(factory: PaymentFactory, amount: float) -> None:
payment = factory.create_payment()
payment.pay(amount)

工程化变体:注册式工厂#

在 Python 工程中,更常见的是“注册表 + 工厂方法”的组合:

class PaymentFactory:
_registry: dict[str, type[Payment]] = {}
@classmethod
def register(cls, name: str, payment_cls: type[Payment]) -> None:
cls._registry[name] = payment_cls
@classmethod
def create(cls, name: str) -> Payment:
return cls._registry[name]()

这种写法本质上仍然是工厂方法,只是把“选择逻辑”配置化、数据化了。


什么时候不该使用工厂方法#

需要警惕以下情况:

  • 只有一个实现,且几乎不会变化
  • 创建过程没有任何条件或配置差异
  • 你只是为了“消灭 if”而引入抽象

如果创建逻辑本身没有变化点,那么抽象只会制造噪声。

如果你只记住一件事: 工厂方法的价值不在于创建对象,而在于隔离变化。


从工厂方法到抽象工厂:什么时候升级#

当你开始遇到以下情况时,工厂方法往往已经不够用了:

  • 一个工厂开始负责创建多个对象
  • 这些对象之间存在必须保持一致的组合关系
  • 你开始在调用方手动保证“不要混用”

此时,问题已经从“创建哪一个对象”升级为:

“选择哪一整套实现”

这正是抽象工厂的适用场景。


抽象工厂(Abstract Factory)#

设计动机#

抽象工厂要解决的核心问题是:

在不暴露具体类的前提下,创建一整套彼此兼容的对象。

关键词不在“抽象”,而在于:产品族一致性


工厂方法 vs 抽象工厂#

维度工厂方法抽象工厂
创建对象数量一个一组
关注点单个产品产品族
扩展方向新产品新系列
变化方式增加实现切换整套实现

判断标准只有一句话:

如果你关心的是“这些对象是否必须来自同一套实现”, 那你需要的通常是抽象工厂。


Python 示例#

产品接口(多个)

class ChatModel(ABC):
@abstractmethod
def chat(self, text: str) -> str:
pass
class EmbeddingModel(ABC):
@abstractmethod
def embed(self, text: str) -> list[float]:
pass

产品族实现

class OpenAIChat(ChatModel):
def chat(self, text: str) -> str:
return f"OpenAI chat: {text}"
class OpenAIEmbedding(EmbeddingModel):
def embed(self, text: str) -> list[float]:
return [0.1, 0.2]
class LocalChat(ChatModel):
def chat(self, text: str) -> str:
return f"Local chat: {text}"
class LocalEmbedding(EmbeddingModel):
def embed(self, text: str) -> list[float]:
return [0.9, 0.8]

抽象工厂接口与实现

class ModelFactory(ABC):
@abstractmethod
def create_chat(self) -> ChatModel:
pass
@abstractmethod
def create_embedding(self) -> EmbeddingModel:
pass
class OpenAIFactory(ModelFactory):
def create_chat(self) -> ChatModel:
return OpenAIChat()
def create_embedding(self) -> EmbeddingModel:
return OpenAIEmbedding()

抽象工厂的工程价值#

  1. 组合合法性由结构保证,而不是由人为约定
  2. 系列切换是原子操作,只需替换工厂实例
factory = OpenAIFactory() # 一行切换整套实现

这在模型后端切换、运行环境差异、能力分级等场景中尤为常见。


什么时候不该使用抽象工厂#

  • 产品之间不存在强一致性要求
  • 交叉组合本身是合理且必要的
  • 产品族边界尚不稳定

过早引入抽象工厂,往往意味着过早冻结系统结构。

如果你只记住一件事: 抽象工厂的价值不在于“创建很多对象”,而在于“约束组合方式”。

笔记·设计模式:工厂方法与抽象工厂
https://blog.snowy.moe/posts/53670/
作者
Muika
发布于
2026-02-06
许可协议
CC BY-NC-SA 4.0