简单工厂模式:Python实现、优缺点分析、违反开闭原则的讨论
简单工厂模式,说白了就是一个“代工厂”。你告诉它你要什么,它给你造出来。你不需要关心对象是怎么new出来的,也不用管构造参数有多复杂。我刚开始接触设计模式时,觉得这玩意儿太简单了,不就是把if-else封装一下嘛?后来在项目中用多了才发现,简单工厂虽然“简单”,但用好了是真省事,用不好也是真坑人。
什么是简单工厂模式?
简单工厂模式不属于GoF的23种经典设计模式,但它却是最常用、最基础的一种创建型模式。它的核心思想是:用一个专门的工厂类来负责创建产品对象,客户端只需要传入一个参数,工厂类就能返回对应的产品实例。
你想想看,如果没有工厂,客户端代码里到处都是if type == "A": return A() elif type == "B": return B(),那代码得多难看?而且一旦新增产品,所有调用方都得改一遍。简单工厂就是把这些创建逻辑集中到一处管理。
- 工厂(Factory):负责创建产品的核心类,通常包含一个静态方法
- 抽象产品(Product):所有具体产品的父类或接口
- 具体产品(ConcreteProduct):工厂实际创建的对象
Python实现:一个日志记录器的例子
我记得有一次做监控系统,需要支持多种日志输出方式:控制台、文件、网络。当时我就用简单工厂模式来管理这些日志记录器。来看代码:
from abc import ABC, abstractmethod
from datetime import datetime
# 抽象产品
class Logger(ABC):
@abstractmethod
def log(self, message: str):
pass
# 具体产品:控制台日志
class ConsoleLogger(Logger):
def log(self, message: str):
print(f"[{datetime.now()}] Console: {message}")
# 具体产品:文件日志
class FileLogger(Logger):
def __init__(self, filename="app.log"):
self.filename = filename
def log(self, message: str):
with open(self.filename, "a", encoding="utf-8") as f:
f.write(f"[{datetime.now()}] File: {message}\n")
# 具体产品:网络日志
class NetworkLogger(Logger):
def __init__(self, endpoint="http://localhost:8080/log"):
self.endpoint = endpoint
def log(self, message: str):
# 实际项目中这里会发HTTP请求
print(f"[{datetime.now()}] Network: {message} -> {self.endpoint}")
# 简单工厂
class LoggerFactory:
@staticmethod
def create_logger(logger_type: str) -> Logger:
if logger_type == "console":
return ConsoleLogger()
elif logger_type == "file":
return FileLogger()
elif logger_type == "network":
return NetworkLogger()
else:
raise ValueError(f"不支持的日志类型: {logger_type}")
# 客户端使用
if __name__ == "__main__":
logger = LoggerFactory.create_logger("console")
logger.log("系统启动成功")
logger = LoggerFactory.create_logger("file")
logger.log("写入文件日志")
嗯,这里要注意:工厂方法通常设计成静态方法,这样客户端不需要实例化工厂就能直接使用。我个人习惯把工厂类设计成不可实例化的,比如加一个私有构造器,但Python里没有真正的私有构造器,所以约定用@staticmethod就够了。
简单工厂模式的优缺点
我在项目中用过不少次简单工厂,它的优缺点我总结了一下:
| 优点 | 缺点 |
|---|---|
| 客户端代码简洁,不需要知道具体产品类名 | 工厂类职责过重,所有产品创建逻辑集中在一起 |
| 创建逻辑集中管理,便于统一修改 | 新增产品必须修改工厂类,违反开闭原则 |
| 适合产品种类较少且稳定的场景 | 产品类型增多时,工厂类会变得臃肿 |
| 实现简单,上手快,几乎没有学习成本 | 静态方法无法被继承和重写,扩展性受限 |
违反开闭原则的讨论
开闭原则(Open-Closed Principle)说:对扩展开放,对修改关闭。简单工厂模式恰恰违反了这一点。为什么?
你看上面的LoggerFactory.create_logger方法,里面有一个if-else链。如果现在要新增一个“数据库日志记录器”,我必须做两件事:
- 新建一个
DatabaseLogger类 - 修改
LoggerFactory,在if-else里加一个分支
第二步就是“修改”已有的代码。你想想看,每次新增产品都要改工厂类,这工厂类就会越来越脆弱。我曾经在一个项目中接手过一个简单工厂,里面竟然有30多个if-else分支,改一个bug要排查半天,那叫一个酸爽。
那是不是说简单工厂模式就一无是处?也不是。违反开闭原则不一定是坏事,关键看场景:
- 产品种类稳定:如果产品类型几乎不变,比如只有console和file两种日志,那简单工厂完全够用
- 产品创建逻辑简单:如果每个产品的创建过程只有一两行代码,用简单工厂反而更清晰
- 团队规模小:小团队快速迭代时,简单工厂的“简单”本身就是优势
说白了,设计模式是工具,不是教条。我见过有人为了“遵守开闭原则”,硬是把只有3个产品的简单工厂改成了抽象工厂模式,结果代码量翻了三倍,可读性反而下降了。没必要。
简单工厂模式的核心逻辑图
下面这张图展示了简单工厂模式的核心流程:客户端通过工厂类创建产品,工厂内部根据参数判断返回哪个具体产品。
从图中可以看得很清楚:客户端只依赖工厂和抽象产品,具体产品对客户端是隐藏的。这就是简单工厂的核心价值——封装变化。但问题也出在工厂内部:那个判断逻辑是硬编码的,每次新增产品都得改它。
什么时候该用简单工厂?
我个人总结了三个适用场景:
- 产品种类少且稳定:比如一个系统只支持两种数据库(MySQL和PostgreSQL),而且短期内不会增加
- 产品创建逻辑简单:每个产品的初始化只需要几个参数,没有复杂的依赖关系
- 客户端不想知道具体产品类名:通过字符串或枚举来指定产品类型,降低耦合
反过来,如果产品种类经常变化,或者每个产品的创建过程差异很大(有的需要读取配置文件,有的需要连接数据库),那就别用简单工厂了。这时候工厂方法模式或抽象工厂模式会更合适。
product_map = {"console": ConsoleLogger, "file": FileLogger}的字典,新增产品时只需要往字典里加一项,不用改if-else。虽然本质上还是修改了工厂类,但至少代码更优雅一些。
好了,关于简单工厂模式就聊这么多。记住一点:简单工厂不是银弹,它只是一个工具。用对了地方,它能帮你写出干净、可维护的代码;用错了地方,它会让你的工厂类变成一个“上帝类”,谁都不敢动。
公众号:蓝海资料掘金营,微信deep3321