CS-Notes 设计模式解析:工厂方法(Factory Method)——由子类决定实例化哪个类的对象创建模式
【免费下载链接】CS-Notes:books: 技术面试必备基础知识、Leetcode、计算机操作系统、计算机网络、系统设计项目地址: https://gitcode.com/GitHub_Trending/cs/CS-Notes
工厂方法(Factory Method)是 CS-Notes 设计模式系列中创建型模式的核心知识点之一,与本仓库 设计模式 - 简单工厂、设计模式 - 抽象工厂 文档相互呼应,共同构成"工厂家族"的完整学习链路。本文以 原始文档 为骨架,结合同一仓库中 设计模式全篇合集 对工厂方法章节的完整展开,从模式意图、类图结构、Java 实现、与简单工厂/抽象工厂的边界辨析到 JDK 标准库中的真实应用,逐层解析,让读者既能在面试中讲清"为什么工厂方法要把实例化推迟到子类",也能在实际编码中判断何时该引入该模式。
一、模式意图:把"实例化哪个类"的决定权下沉给子类
原始文档对工厂方法 Intent 的概括非常精炼:
定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。
拆解这句话可以得到两个关键点:
- "定义了一个创建对象的接口"——在抽象父类(或接口)中声明一个用于创建产品的方法(即
factoryMethod()),它只约定"返回一个 Product",而不关心具体返回哪一个产品实现; - "由子类决定要实例化哪个类"——具体返回
ConcreteProduct、ConcreteProduct1还是ConcreteProduct2,由继承该父类的不同子类工厂各自覆盖实现来决定,父类代码完全不感知具体产品类型。
这就是"实例化操作推迟到子类"的含义:对象创建时机的决策点从使用方/父类移到了子类继承体系上,父类只在抽象层面描述"我需要一个产品"。
public abstract class Factory { abstract public Product factoryMethod(); public void doSomething() { Product product = factoryMethod(); // do something with the product } }二、演进动机:为什么是"由子类来创建对象"
原始文档在 Class Diagram 一节开宗明义地指出了它与简单工厂的本质差异:
在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。
在简单工厂方案里,所有实例化逻辑(if-else选择具体产品)被集中收敛进一个独立的SimpleFactory类,客户端不再直接new具体产品,而是把"要哪种产品"的参数交给工厂:
public class SimpleFactory { public Product createProduct(int type) { if (type == 1) { return new ConcreteProduct1(); } else if (type == 2) { return new ConcreteProduct2(); } return new ConcreteProduct(); } }简单工厂解决了"客户类与具体子类实现解耦"的问题,但选择逻辑依然集中在工厂内部的一段分支代码上。每增加一种产品,仍要修改SimpleFactory.createProduct()本身。
工厂方法的思路则是把"选择"改成"覆盖":不再由某个旁路工厂类的if-else决定返回哪个产品,而是每种具体产品都对应一个具体工厂子类,父类只声明抽象的factoryMethod(),由继承它的具体子类各自决定实例化哪个产品。这样,"决定要实例化哪个类"的逻辑就被分散、固化到了每个子类中,新增产品时通过新增一个具体工厂子类来扩展,而无需改动既有工厂代码。
下图是 简单工厂的类图 对应的结构(Client依赖SimpleFactory,由SimpleFactory统一创建ConcreteProduct系列):
与之形成对照的是工厂方法的类图(见下节),创建职责从"另一个独立类"转移到了"工厂自身的子类"。
三、结构解析:四个角色的职责划分
原始文档给出的类图用到了Factory与ConcreteFactory两级继承结构,图示文件保存在 notes/pics/f4d0afd0-8e78-4914-9e60-4366eaf065b5.png:
对照类图,模式中通常包含以下角色:
| 角色 | 对应类 | 职责 |
|---|---|---|
| 产品抽象 | Product | 定义产品的抽象类型,是工厂方法返回值的静态类型 |
| 具体产品 | ConcreteProduct/ConcreteProduct1/ConcreteProduct2 | 实现Product的具体产品类 |
| 抽象工厂(Creator) | Factory | 声明抽象的factoryMethod(): Product,并定义使用产品的业务方法doSomething() |
| 具体工厂(ConcreteCreator) | ConcreteFactory/ConcreteFactory1/ConcreteFactory2 | 覆盖factoryMethod(),返回各自对应的具体产品 |
原始文档对类图的关键讲解是:
下图中,Factory 有一个 doSomething() 方法,这个方法需要用到一个产品对象,这个产品对象由 factoryMethod() 方法创建。该方法是抽象的,需要由子类去实现。
这段描述包含了一个容易忽略的细节:doSomething()是父类里"已经写好的业务骨架",而它所需的产品却要在运行时才能确定。父类的doSomething()在编译期只知道"调用factoryMethod()会得到一个Product",至于这个Product究竟是哪一种实现,要到运行期由对象的真实类型(哪个具体工厂子类)来决定。
细看可以发现,doSomething()定义流程、把其中"创建产品"这一步骤延迟到子类实现的结构,与本仓库 设计模式 - 模板方法 中"CaffeineBeverage 定义算法框架、将部分步骤延迟到子类"的思路一致——这也是工厂方法常被看作一种特殊的模板方法应用的原因。
四、Java 实现逐块解读:抽象父类 + 多个具体子类工厂
原始文档共给出 4 段核心代码,构成一套完整的工厂方法骨架。下面逐段解读,并补充让整套代码可独立编译运行所必需的产品类定义与调用方示例。
4.1 抽象工厂:声明工厂方法 + 复用业务方法
public abstract class Factory { abstract public Product factoryMethod(); public void doSomething() { Product product = factoryMethod(); // do something with the product } }要点:
factoryMethod()被声明为抽象方法,强制所有子类工厂给出"我生产哪种产品"的实现;doSomething()是父类提供的可复用业务方法,它只面向Product抽象编程,通过调用factoryMethod()获得产品后继续处理;- 由于
doSomething()写在了父类中,所有具体工厂子类都能继承同一套业务逻辑,区别只在最终拿到的是哪种产品。
4.2 具体工厂:每种产品对应一个子类工厂
public class ConcreteFactory extends Factory { public Product factoryMethod() { return new ConcreteProduct(); } }public class ConcreteFactory1 extends Factory { public Product factoryMethod() { return new ConcreteProduct1(); } }public class ConcreteFactory2 extends Factory { public Product factoryMethod() { return new ConcreteProduct2(); } }三个具体工厂分别继承Factory并覆盖factoryMethod(),各自返回ConcreteProduct、ConcreteProduct1、ConcreteProduct2。调用方只要持有某个具体工厂的实例,就能稳定地得到它对应的产品——实例化逻辑被封装在每个具体工厂内部,与doSomething()的业务逻辑完全解耦。
4.3 补齐产品定义,形成可运行的完整示例
工厂方法文档本身只关心工厂侧代码,其引用的Product、ConcreteProduct等类型在本仓库 简单工厂文档 的示例中有明确定义,可直接复用同一套产品骨架:
public interface Product { }public class ConcreteProduct implements Product { }public class ConcreteProduct1 implements Product { }public class ConcreteProduct2 implements Product { }在此基础上,调用方(Client)只需要面向抽象工厂Factory编程,选择用哪个具体工厂,就会在doSomething()内部获得对应产品:
public class Client { public static void main(String[] args) { Factory factory = new ConcreteFactory1(); factory.doSomething(); // 内部通过 factoryMethod() 得到 ConcreteProduct1 Factory factory2 = new ConcreteFactory2(); factory2.doSomething(); // 内部通过 factoryMethod() 得到 ConcreteProduct2 } }这段调用方代码体现了两层解耦:
- Client 不直接
new ConcreteProduct1,甚至不引用任何具体产品类名; doSomething()的业务逻辑完全不依赖具体产品,把产品创建这一步骤交给子类按需实现。
五、与抽象工厂的边界:单对象 vs 相关对象家族
工厂方法与抽象工厂是工厂家族中最容易被混淆的一对。本仓库 抽象工厂文档 专门对此做了辨析,原文要点如下:
抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。
抽象工厂模式用到了工厂方法模式来创建单一对象,AbstractFactory 中的 createProductA() 和 createProductB() 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂方法模式的定义。
从高层次来看,抽象工厂使用了组合,即 Client 组合了 AbstractFactory,而工厂方法模式使用了继承。
可以归纳出三条清晰的边界:
| 对比维度 | 工厂方法 | 抽象工厂 |
|---|---|---|
| 创建数量 | 只创建一个产品对象 | 创建一组相关的产品对象(对象家族) |
| 实现手段 | Client/父类面向Factory,通过继承让子类覆盖工厂方法 | Client组合AbstractFactory,一次调用多个创建方法得到整套对象族 |
| 层次关系 | 基础模式 | 上层模式,其内部每个createProductX()单独看就是一个工厂方法 |
抽象工厂的类图可以直观看出"一个具体工厂对应多个产品"的差别(图示文件:notes/pics/e2190c36-8b27-4690-bde5-9911020a1294.png):
理解二者关系有助于确定选型:当只需要"一个方法产出一种对象",用工厂方法即可;当业务上必须成组地创建相互关联的多个对象(如一套 UI 风格下的按钮+输入框),则应升级为抽象工厂,且抽象工厂内部依然在用工厂方法组织单对象创建逻辑。
六、工厂方法在 JDK 标准库中的应用
原始文档在 JDK 一节给出了 7 个典型应用。这些 API 的共同特征是:公开的创建入口往往返回抽象类型的实例,而真正实例化哪个子类由内部依据时区、语言环境、参数等运行时信息决定,调用方无从也不必感知:
| JDK 类与方法 | 工厂方法的体现 |
|---|---|
java.util.Calendar#getInstance() | Calendar是抽象类,getInstance()依据当前时区与语言环境返回其某个具体子类实例,调用方只面向Calendar编程 |
java.util.ResourceBundle#getBundle(String) | 根据资源基名与语言环境定位并返回对应的资源包子类实例 |
java.text.NumberFormat#getInstance() | 返回基于默认语言环境的具体数字格式器(如DecimalFormat)实例 |
java.nio.charset.Charset#forName(String) | 按字符集名称返回对应的Charset实例 |
java.net.URLStreamHandlerFactory#createURLStreamHandler(String) | 定义创建URLStreamHandler的工厂接口,由不同协议的具体实现提供对应处理器 |
java.util.EnumSet#of(E...) | 以静态工厂形式创建枚举集合,并会按枚举规模的差异返回不同的内部实现子类 |
javax.xml.bind.JAXBContext#createMarshaller() | 由上下文对象统一创建Marshaller实例,屏蔽底层构造细节 |
这些例子共同说明工厂方法在标准库中的典型落点:当某个库希望把"创建哪个实现类"的决策保留给自己、只向外界暴露抽象类型时,工厂方法是最自然的封装方式——这也正是阅读源码时识别该模式的信号。
七、使用场景与权衡小结
综合原始文档的意图描述与仓库中同系列文档的对照,可以总结出以下判断框架:
适合使用工厂方法的情形
- 父类中存在一段不依赖具体产品的公共业务逻辑(对应
doSomething()),希望在逻辑内部获得产品对象,但不希望父类耦合具体产品类; - 产品的具体类型预期会持续扩展,希望"新增产品 = 新增一个具体产品类 + 新增一个具体工厂子类",尽量不改动既有调用方与既有工厂;
- 希望将对象创建的细节(
new哪个类)从使用方代码中剥离,让 Client 只面向Product与Factory抽象编程。
需要权衡的点
- 每引入一种具体产品通常就要新增一个具体工厂类,类的数量会随之增长,简单场景下反而显得繁琐;
- 当需求演进为"必须成组创建相互关联的多对象家族"时,单靠工厂方法已不足够,应参考 抽象工厂 的组合式设计;
- 若产品类型不多、且创建逻辑变化频率低,直接使用 简单工厂 的集中式
createProduct(type)可能更轻量。
八、仓库内延伸学习路径
- 阅读 设计模式 - 工厂方法.md 原始笔记,对照本节类图与代码;
- 在 设计模式全篇合集(notes/设计模式.md) 中查看工厂方法章节及前后文语境(简单工厂紧随其后是工厂方法与抽象工厂);
- 通过 设计模式目录索引 定位创建型模式的完整目录,顺次阅读 简单工厂 → 工厂方法 → 抽象工厂 形成"工厂家族"的递进认知;
- 与 生成器(Builder)、单例 等其他创建型模式对比,理解"何时按步骤构造对象、何时全局唯一、何时交由子类决定实例化类型"的各自定位。
【免费下载链接】CS-Notes:books: 技术面试必备基础知识、Leetcode、计算机操作系统、计算机网络、系统设计项目地址: https://gitcode.com/GitHub_Trending/cs/CS-Notes
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考