工厂方法一定比简单工厂高级?三种工厂模式到底该怎么选

「设计模式与范式」系列 Day10

写在前面

简单工厂、工厂方法、抽象工厂,光看名字很容易被带偏------好像后面的一定比前面的"高级",写代码就该一步到位上抽象工厂。这篇用一个规则配置解析器的真实需求走一遍:不同格式(JSON/XML/YAML/Properties)的配置文件要用不同的 Parser 去解析,看看这三种工厂到底是怎么演进出来的,以及为什么"更复杂"不等于"更好"。


一、是什么:三种工厂各自解决的问题

模式 解决的问题 对象的分类维度
简单工厂 一个工厂类,按参数创建对应的实现类 一个维度(比如按文件格式)
工厂方法 用多态代替 if-else,每种类型对应一个工厂类 一个维度,但用继承体系表达
抽象工厂 一个工厂负责创建一组相关的对象 两个及以上维度

三者共同的目标没有变过:把"根据什么条件创建哪个对象"这件事从业务代码里挪出去。区别只在于对象的分类维度有多少个、以及创建逻辑复杂到什么程度。


二、为什么:从简单工厂演进到抽象工厂,每一步都是被具体问题推着走的

起点:规则配置文件要按格式选择解析器

java 复制代码
public class RuleConfigSource {
    public RuleConfig load(String filePath) {
        String extension = getFileExtension(filePath); // json/xml/yaml/properties
        IRuleConfigParser parser = RuleConfigParserFactory.createParser(extension);
        if (parser == null) {
            throw new InvalidRuleConfigException("unsupported: " + filePath);
        }
        String configText = ""; // 从文件读取
        return parser.parse(configText);
    }
}

createParser 该怎么实现,取决于创建逻辑复不复杂:如果只是简单的按格式 new 一个对应的 Parser,用简单工厂 就够了;如果创建过程本身很重,才需要往工厂方法升级。

简单工厂:够用就不要多加抽象

java 复制代码
public class RuleConfigParserFactory {
    public static IRuleConfigParser createParser(String format) {
        if ("json".equalsIgnoreCase(format)) return new JsonRuleConfigParser();
        if ("xml".equalsIgnoreCase(format)) return new XmlRuleConfigParser();
        // yaml、properties 同理
        return null;
    }
}

这个 if-else 版本每次调用都要重新 new 一个 Parser 对象。如果 Parser 本身没有成员变量、可以安全复用,还有一种更省资源的写法------预先创建好、缓存在 Map 里:

java 复制代码
public class RuleConfigParserFactory {
    private static final Map<String, IRuleConfigParser> cachedParsers = new HashMap<>();
    static {
        cachedParsers.put("json", new JsonRuleConfigParser());
        cachedParsers.put("xml", new XmlRuleConfigParser());
        // yaml、properties 同理
    }
    public static IRuleConfigParser createParser(String format) {
        if (format == null || format.isEmpty()) return null;
        return cachedParsers.get(format.toLowerCase());
    }
}

两种写法都是简单工厂,选哪种只取决于 Parser 是否无状态、能不能复用,跟"该不该用简单工厂"这个问题无关。

工厂方法:创建逻辑变复杂时,把工厂本身也拆开

如果某种 Parser 的创建过程不再是一个 new 能打发的------比如要组合别的对象、要做额外的初始化------继续把所有创建逻辑塞进一个工厂类,这个类会越来越臃肿。工厂方法模式用多态把每种类型的创建逻辑拆到各自的工厂类里:

java 复制代码
public interface IRuleConfigParserFactory {
    IRuleConfigParser createParser();
}
public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory {
    public IRuleConfigParser createParser() { return new JsonRuleConfigParser(); }
}
// XmlRuleConfigParserFactory、YamlRuleConfigParserFactory 同理

但这样一来,load() 里又多了一段新的 if-else------用来判断该 new 哪个工厂类:

java 复制代码
IRuleConfigParserFactory factory =
        "json".equalsIgnoreCase(ext) ? new JsonRuleConfigParserFactory() : /* ... */ null;

创建对象的逻辑被挪走了,创建工厂 的逻辑又耦合回了 load()。解法很直接:再套一层简单工厂,去创建这些工厂对象,工厂类本身通常不含状态,用 Map 缓存复用即可:

java 复制代码
public class RuleConfigParserFactoryMap { // 工厂的工厂
    private static final Map<String, IRuleConfigParserFactory> cachedFactories = new HashMap<>();
    static {
        cachedFactories.put("json", new JsonRuleConfigParserFactory());
        cachedFactories.put("xml", new XmlRuleConfigParserFactory());
    }
    public static IRuleConfigParserFactory getParserFactory(String type) {
        return type == null ? null : cachedFactories.get(type.toLowerCase());
    }
}

load() 只需要调用 RuleConfigParserFactoryMap.getParserFactory(ext).createParser(),不再关心具体是哪个工厂类。

抽象工厂:当分类维度不止一个

前面的场景里,Parser 只有"文件格式"这一个分类维度。假设现在配置文件不只有规则配置(Rule),还有系统配置(System),两个维度交叉起来,用工厂方法就要写 Json/Xml/Yaml/Properties × Rule/System 一共 8 个工厂类。抽象工厂就是为了应对这种场景:让一个工厂负责创建一组相关的对象,而不是一种。

java 复制代码
public interface IConfigParserFactory {
    IRuleConfigParser createRuleParser();
    ISystemConfigParser createSystemParser();
}
public class JsonConfigParserFactory implements IConfigParserFactory {
    public IRuleConfigParser createRuleParser() { return new JsonRuleConfigParser(); }
    public ISystemConfigParser createSystemParser() { return new JsonSystemConfigParser(); }
}
// XmlConfigParserFactory 同理,8个工厂类收敛成了4个

三种工厂的调用结构对比:

flowchart LR subgraph 简单工厂 一个工厂类按参数创建 A1[调用方] --> A2[RuleConfigParserFactory] A2 --> A3[具体Parser] end subgraph 工厂方法 每种类型一个工厂类 B1[调用方] --> B2[对应类型的ParserFactory] B2 --> B3[具体Parser] end subgraph 抽象工厂 一个工厂造一组相关对象 C1[调用方] --> C2[JsonConfigParserFactory] C2 --> C3[RuleParser] C2 --> C4[SystemParser] end

三、怎么用:判断标准和容易混淆的边界

什么时候该引入工厂模式

对照 Day06 的判断标准,出现下面任意一种情况,才值得把创建逻辑抽到工厂类里,而不是提前套用:

  • 代码里存在 if-else 分支,需要根据条件动态创建不同的对象
  • 单个对象本身的创建过程就很复杂(要组合其他对象、要做额外初始化),哪怕不需要按类型区分

引入之后能拿到的收益,也可以反过来当作判断依据:封装变化 (创建逻辑变了,调用方无感知)、代码复用 (创建逻辑抽成独立类后能被多处复用)、隔离复杂性 (调用方不用了解创建细节)、控制复杂度(原来的函数/类职责更单一了)。四条里一条都不占,多半就是过度设计。

简单工厂 vs 工厂方法:别看名字选,看创建逻辑复不复杂

创建逻辑简单(判断类型、直接 new)用简单工厂;创建逻辑复杂到值得拆分(一个工厂类会因此变得臃肿)才用工厂方法。工厂方法不是简单工厂的"升级版",是简单工厂扛不住的场景下的下一个台阶,两者没有谁比谁更正确。

工厂模式和 DI 容器不是一回事

Spring 这类 DI 容器的底层设计思路确实是基于工厂模式的------本质上是一个"大工厂",在应用启动时根据配置创建好所有需要的对象,用的时候直接从容器里取。但两者管的范围不一样:一个工厂类只负责某个类或某一组相关类的创建;DI 容器负责整个应用所有类对象的创建,还要管配置解析和对象生命周期(比如 Spring 里 scope=singleton/prototype、lazy-init、init-method/destroy-method 这些配置)。工厂类的创建逻辑是写死在代码里的,DI 容器的创建逻辑是从配置动态读出来的,这也是为什么工厂类的代码量会随着类的个数线性增长,而 DI 容器不会------它靠反射机制在运行时动态加载类、创建对象。


四、面试追问

Q1:简单工厂、工厂方法、抽象工厂,本质上的区别是什么?

简单工厂用一个工厂类按参数创建对应的实现类,适合创建逻辑简单的场景;工厂方法用多态把每种类型的创建逻辑拆到各自的工厂类里,适合单个对象创建过程本身就复杂、放一起会让工厂类臃肿的场景;抽象工厂应对的是对象有两个及以上分类维度的场景,让一个工厂负责创建一组相关的对象,避免维度交叉导致工厂类数量爆炸。三者共同的目标都是把"按什么条件创建哪个对象"从业务代码里挪出去。

Q2:工厂方法模式一定比简单工厂模式更好吗?

不是。工厂方法不是简单工厂的升级版,而是简单工厂在某类场景下会失效时的下一个台阶。创建逻辑简单时,硬套工厂方法只会多出一堆只有一个实现的工厂类,属于过度设计;只有当创建逻辑复杂到会让单个工厂类变得臃肿时,才值得拆分成多个工厂类。

Q3:引入工厂方法之后,为什么又出现了 "工厂的工厂"?它解决了什么问题?

工厂方法把对象的创建逻辑拆到了多个工厂类里,但调用方要先决定该 new 哪个工厂类,这段判断逻辑又耦合回了业务代码。"工厂的工厂"是再套一层简单工厂,专门负责创建这些工厂对象------因为工厂类通常不含状态,可以用 Map 缓存好直接复用,调用方就完全不用关心具体是哪个工厂类了。

Q4:什么时候该考虑引入工厂模式?

两种典型信号:一是代码里出现 if-else 分支,需要根据条件动态创建不同的对象;二是即便不需要按类型区分,单个对象本身的创建过程就足够复杂(组合其他对象、做额外初始化)。判断是否真的该用,还可以对照四个收益------封装变化、代码复用、隔离复杂性、控制复杂度------一条都占不上,大概率是过度设计。

Q5:工厂模式和 DI 容器有什么区别?

DI 容器的底层思路确实基于工厂模式,可以理解为一个管理整个应用所有对象的"大工厂",但两者管辖范围不同:工厂类只负责某个类或某一组相关类的创建,创建逻辑写死在代码里;DI 容器负责整个应用所有对象的创建、配置解析和生命周期管理,创建逻辑是从配置动态读取的,并且通过反射在运行时动态加载类,代码量不会随类的个数线性增长。


下一篇预告

Day11 建造者模式:什么时候构造函数该退休,交给 Builder。

相关推荐
高晶18 小时前
CANopen 在 BMS 系统中的 C 语言实战指南
设计模式·程序员
博风19 小时前
13、设计模式应用举例:工厂方法模式:创建药品对象
设计模式·工厂方法模式
代钦塔拉21 小时前
【C++/Qt 进阶】设计模式系列(5):适配器模式——给“不兼容的接口“贴一层翻译膜
c++·qt·设计模式
bigbread2 天前
策略模式 + 工厂方法模式在项目中的实战应用
设计模式
高晶2 天前
C 语言实现 BMS 系统中 SPI 通信协议实战指南
设计模式·程序员
代钦塔拉2 天前
【C++/Qt 进阶】设计模式系列(2):工厂模式——从游戏角色到披萨店
c++·算法·设计模式
高晶2 天前
2A3 单端甲类功放从零制作全攻略:电路设计、焊接调试到煲机维护一站式指南
前端·设计模式
代钦塔拉2 天前
【C++/Qt 进阶】设计模式系列(4):原型模式——用“克隆“代替“重新构造“
c++·qt·设计模式
博风2 天前
11、设计模式应用举例:迭代器模式:使用多个集合存储对象
设计模式·迭代器模式
日和雫2 天前
面向对象与面向过程,谁才是“银弹”?
设计模式