10、工厂方法模式:Python实现、角色组成(Product、Creator)、扩展性分析
工厂方法模式,说白了就是「把对象的创建延迟到子类」。
我刚开始接触设计模式时,总觉得工厂模式不就是 new 一下对象吗?搞那么复杂干嘛。直到我在一个项目中维护了上千行 if-else 的创建逻辑,每次加新产品都要改核心代码,改一次崩一次……嗯,从那以后,我彻底理解了工厂方法的价值。
10.1 模式的核心角色
工厂方法模式有四个核心参与者。我习惯把它们分成两组来看:
| 角色 | 名称 | 职责 |
|---|---|---|
| 抽象产品 | Product | 定义产品的公共接口 |
| 具体产品 | ConcreteProduct | 实现产品接口的具体类 |
| 抽象创建者 | Creator | 声明工厂方法,返回 Product 对象 |
| 具体创建者 | ConcreteCreator | 重写工厂方法,返回具体的 ConcreteProduct |
你想想看,这里最关键的一点是什么?Creator 并不知道它要创建哪个具体产品。它只负责定义「怎么创建」的流程,而「创建什么」交给子类决定。
核心原则:依赖倒置。高层模块(Creator)不依赖低层模块(ConcreteProduct),两者都依赖抽象(Product)。
10.2 Python 实现:一个日志记录器的例子
我记得有一次做日志系统重构,需要支持文件日志、控制台日志、远程日志。如果用简单工厂,每次加新日志类型都要改工厂类。用工厂方法就优雅多了。
先看抽象产品:
from abc import ABC, abstractmethod
class Logger(ABC):
"""抽象产品:日志记录器"""
@abstractmethod
def log(self, message: str) -> None:
pass
具体产品:
class FileLogger(Logger):
def log(self, message: str) -> None:
print(f"[文件日志] {message}")
# 实际项目中这里会写入文件
class ConsoleLogger(Logger):
def log(self, message: str) -> None:
print(f"[控制台日志] {message}")
class RemoteLogger(Logger):
def log(self, message: str) -> None:
print(f"[远程日志] {message}")
# 实际项目中这里会发送 HTTP 请求
抽象创建者:
class LoggerCreator(ABC):
"""抽象创建者:定义工厂方法"""
@abstractmethod
def create_logger(self) -> Logger:
pass
def write_log(self, message: str) -> None:
# 这里可以包含公共的业务逻辑
logger = self.create_logger()
logger.log(message)
具体创建者:
class FileLoggerCreator(LoggerCreator):
def create_logger(self) -> Logger:
return FileLogger()
class ConsoleLoggerCreator(LoggerCreator):
def create_logger(self) -> Logger:
return ConsoleLogger()
class RemoteLoggerCreator(LoggerCreator):
def create_logger(self) -> Logger:
return RemoteLogger()
使用起来非常直观:
def client_code(creator: LoggerCreator) -> None:
creator.write_log("系统启动成功")
# 客户端根据需要选择创建者
client_code(FileLoggerCreator())
client_code(ConsoleLoggerCreator())
个人习惯:我通常把 Creator 中的公共逻辑(比如 write_log 方法)做得尽量通用。这样子类只需要关注 create_logger 这一个点,职责非常清晰。
10.3 扩展性分析
工厂方法模式的扩展性,说白了就是「开闭原则」的完美体现。
扩展方式一:增加新产品
假设现在要加一个「数据库日志」:
class DatabaseLogger(Logger):
def log(self, message: str) -> None:
print(f"[数据库日志] {message}")
class DatabaseLoggerCreator(LoggerCreator):
def create_logger(self) -> Logger:
return DatabaseLogger()
看到了吗?不需要修改任何已有代码。只需要新增两个类:一个产品,一个创建者。这就是开闭原则——对扩展开放,对修改关闭。
扩展方式二:增加创建前的预处理
我曾经遇到一个场景:创建某些产品前需要做权限校验。工厂方法可以轻松应对:
class SecureFileLoggerCreator(FileLoggerCreator):
def create_logger(self) -> Logger:
# 权限校验逻辑
if not self._check_permission():
raise PermissionError("无权限创建文件日志")
return super().create_logger()
def _check_permission(self) -> bool:
# 实际校验逻辑
return True
我曾经踩过的坑:工厂方法虽然扩展性好,但每加一个产品就要加两个类。如果产品种类非常多(比如几十上百个),类数量会爆炸。这时候可以考虑用「反射」或「注册表」来简化。
10.4 工厂方法 vs 简单工厂
很多初学者会混淆这两个概念。我简单说下区别:
- 简单工厂:一个工厂类里写 if-else 或 switch,根据参数返回不同产品。违反开闭原则。
- 工厂方法:每个产品对应一个工厂子类。符合开闭原则,但类数量多。
你想想看,如果你的产品类型稳定不变,简单工厂完全够用。但如果产品经常新增或变化,工厂方法才是正道。
10.5 核心知识体系
下面这张图是我自己总结的工厂方法模式知识结构,帮你快速理清脉络:
10.6 实际项目中的避坑指南
最后分享几个我实际项目中遇到的坑:
- 不要滥用:如果只有一两个产品,用工厂方法反而增加复杂度。简单工厂或直接 new 更合适。
- 注意参数传递:工厂方法如果参数太多,可以考虑用 Builder 模式配合。
- 我曾经犯过的错:在 Creator 中写了产品相关的业务逻辑,导致子类难以复用。记住,Creator 只负责「创建流程」,不负责「产品业务」。
工厂方法模式,说白了就是「把变化封装起来」。你只要记住这个核心思想,什么时候该用它,自然就清楚了。
公众号:蓝海资料掘金营,微信deep3321