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

「设计模式与范式」系列 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。

相关推荐
Hespethorn2 小时前
设计模式是语言缺陷的补丁 —— 从工厂、单例、策略三个模式说起
设计模式
Lost of 程序猿2 小时前
命令模式实战:把“下单后的动作“打包成可执行的任务
后端·设计模式·c#·asp.net·命令模式
一只旭宝2 小时前
【C++复习】四种类型转换 + 常见设计模式 + Redis/MySQL(后端面试复习完整版)
c++·redis·笔记·mysql·设计模式
YYYing.3 小时前
【设计模式系列 (四) 】建造者模式
c++·后端·设计模式·建造者模式·c/c++
Zane199421 小时前
同一段单例代码,为什么在高并发下偶尔会创建出两个实例?聊聊哪种单例实现才是真的安全
设计模式
Carl_奕然1 天前
【智能体】Loop 的四种设计模式之:Agent Loop(2026 最新版)
人工智能·设计模式·语言模型
小酒星小杜1 天前
画 AI 漫画,别只会写“日漫风”:10 种画风、适用故事和可复制提示词
人工智能·设计模式·程序员
sarasuki1 天前
为什么 Agent 工具需要一个「协议」而不是「框架」:MCP 成为标准的底层逻辑
设计模式·agent·mcp
vivo互联网技术1 天前
知识不是文件,也不是向量 | KDC 系列 02
人工智能·设计模式·架构