简单工厂模式: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就够了。

简单工厂模式的优缺点

我在项目中用过不少次简单工厂,它的优缺点我总结了一下:

优点 缺点
客户端代码简洁,不需要知道具体产品类名 工厂类职责过重,所有产品创建逻辑集中在一起
创建逻辑集中管理,便于统一修改 新增产品必须修改工厂类,违反开闭原则
适合产品种类较少且稳定的场景 产品类型增多时,工厂类会变得臃肿
实现简单,上手快,几乎没有学习成本 静态方法无法被继承和重写,扩展性受限
我的经验:简单工厂最适合产品种类不超过5个、且不太可能频繁新增的场景。如果你发现工厂里的if-else超过10个分支,赶紧考虑重构为工厂方法模式或抽象工厂模式吧。

违反开闭原则的讨论

开闭原则(Open-Closed Principle)说:对扩展开放,对修改关闭。简单工厂模式恰恰违反了这一点。为什么?

你看上面的LoggerFactory.create_logger方法,里面有一个if-else链。如果现在要新增一个“数据库日志记录器”,我必须做两件事:

  1. 新建一个DatabaseLogger
  2. 修改LoggerFactory,在if-else里加一个分支

第二步就是“修改”已有的代码。你想想看,每次新增产品都要改工厂类,这工厂类就会越来越脆弱。我曾经在一个项目中接手过一个简单工厂,里面竟然有30多个if-else分支,改一个bug要排查半天,那叫一个酸爽。

避坑指南:我曾经在一个支付系统中使用简单工厂来创建不同的支付渠道(支付宝、微信、银联等)。刚开始只有3个渠道,一切都很美好。半年后渠道增加到8个,工厂类变得又臭又长。更可怕的是,每次新增渠道都要重新测试整个工厂类,因为修改了已有代码。后来我不得不重构为工厂方法模式,每个渠道有自己的工厂子类,才算解脱。

那是不是说简单工厂模式就一无是处?也不是。违反开闭原则不一定是坏事,关键看场景:

  • 产品种类稳定:如果产品类型几乎不变,比如只有console和file两种日志,那简单工厂完全够用
  • 产品创建逻辑简单:如果每个产品的创建过程只有一两行代码,用简单工厂反而更清晰
  • 团队规模小:小团队快速迭代时,简单工厂的“简单”本身就是优势

说白了,设计模式是工具,不是教条。我见过有人为了“遵守开闭原则”,硬是把只有3个产品的简单工厂改成了抽象工厂模式,结果代码量翻了三倍,可读性反而下降了。没必要。

简单工厂模式的核心逻辑图

下面这张图展示了简单工厂模式的核心流程:客户端通过工厂类创建产品,工厂内部根据参数判断返回哪个具体产品。

简单工厂模式核心逻辑 客户端 Client create_logger("console") 简单工厂 LoggerFactory 内部判断逻辑 if type == "console" elif type == "file" elif type == "network" 返回实例 具体产品 ConsoleLogger FileLogger NetworkLogger 抽象产品 Logger 客户端只与工厂和抽象产品打交道,不直接依赖具体产品类

从图中可以看得很清楚:客户端只依赖工厂和抽象产品,具体产品对客户端是隐藏的。这就是简单工厂的核心价值——封装变化。但问题也出在工厂内部:那个判断逻辑是硬编码的,每次新增产品都得改它。

什么时候该用简单工厂?

我个人总结了三个适用场景:

  • 产品种类少且稳定:比如一个系统只支持两种数据库(MySQL和PostgreSQL),而且短期内不会增加
  • 产品创建逻辑简单:每个产品的初始化只需要几个参数,没有复杂的依赖关系
  • 客户端不想知道具体产品类名:通过字符串或枚举来指定产品类型,降低耦合

反过来,如果产品种类经常变化,或者每个产品的创建过程差异很大(有的需要读取配置文件,有的需要连接数据库),那就别用简单工厂了。这时候工厂方法模式或抽象工厂模式会更合适。

一个小技巧:如果你担心未来产品会增多,但又想先用简单工厂快速实现,可以在工厂方法里使用反射或注册表机制。比如维护一个product_map = {"console": ConsoleLogger, "file": FileLogger}的字典,新增产品时只需要往字典里加一项,不用改if-else。虽然本质上还是修改了工厂类,但至少代码更优雅一些。

好了,关于简单工厂模式就聊这么多。记住一点:简单工厂不是银弹,它只是一个工具。用对了地方,它能帮你写出干净、可维护的代码;用错了地方,它会让你的工厂类变成一个“上帝类”,谁都不敢动。


公众号:蓝海资料掘金营,微信deep3321