设计模式:工厂方法模式

欢迎你来读这篇博客,这篇博客主要是关于工厂方法模式
其中包括工厂方法模式的核心思想、适用场景、优缺点、与简单工厂模式的区别,以及一个贴近 Java 后端开发的完整案例。

序言

在上一篇简单工厂模式中,我们已经知道:如果系统中存在多种对象创建逻辑,可以把 new 对象的过程统一放到一个工厂类中,避免业务代码到处写 if elsenew

但是简单工厂模式有一个很现实的问题:

当产品类型不断增加时,工厂类会越来越胖,每次新增产品都要修改同一个工厂类。

这就违反了设计模式六大原则中的开闭原则:对扩展开放,对修改关闭。

工厂方法模式正是为了解决这个问题而出现的。

它的核心思想是:

不再由一个统一的大工厂负责创建所有对象,而是定义一个创建对象的工厂接口,让不同的具体工厂负责创建不同的具体产品。

简单说,简单工厂像一个“万能窗口”,什么业务都找它;工厂方法则像“分业务窗口”,支付找支付工厂,通知找通知工厂,导出找导出工厂。万能窗口迟早排长队,分窗口才适合长期扩展。

正文

chapter 1:什么是工厂方法模式

工厂方法模式,英文是 Factory Method Pattern,属于创建型设计模式。

它定义了一个用于创建对象的接口,但把具体创建哪一个对象的决定权交给子类或者具体工厂类。

经典结构一般包括四个角色:

  1. 抽象产品:定义产品的公共行为。
  2. 具体产品:实现抽象产品接口。
  3. 抽象工厂:定义创建产品的方法。
  4. 具体工厂:实现抽象工厂,负责创建具体产品。

它解决的核心问题不是“如何 new 一个对象”,而是:

如何让对象创建逻辑具备良好的扩展性,并避免业务代码依赖具体实现类。

chapter 2:为什么需要工厂方法模式

先看一个常见问题。

假设系统中有多种通知方式:

  • 邮件通知
  • 短信通知
  • 站内信通知
  • 企业微信通知
  • 钉钉通知

如果使用简单工厂,可能会写成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class NotificationFactory {

public static Notification create(String type) {
if ("email".equals(type)) {
return new EmailNotification();
}

if ("sms".equals(type)) {
return new SmsNotification();
}

if ("wechat".equals(type)) {
return new WeChatNotification();
}

throw new IllegalArgumentException("Unsupported notification type: " + type);
}
}

这个写法并不是不能用。

在小项目里,它甚至很直接、很好理解。问题是,当通知渠道越来越多时,这个类会不断膨胀。每增加一种通知方式,都要修改 NotificationFactory

这会带来几个问题:

  • 工厂类职责越来越重;
  • 修改旧代码容易引入新 bug;
  • 所有产品创建逻辑集中在一个类里,耦合度较高;
  • 不符合开闭原则;
  • 多人协作时容易频繁改同一个文件,代码冲突概率上升。

工厂方法模式的做法是:每一种产品都有自己的工厂。

新增一种通知方式时,不修改旧工厂,而是新增一个具体工厂类。

chapter 3:工厂方法模式的类图理解

工厂方法模式可以用下面的结构理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
             ┌────────────────────┐
│ Notification │
│ send(message) │
└─────────▲──────────┘

┌───────────────┼────────────────┐
│ │ │
┌────────────┐ ┌────────────┐ ┌──────────────┐
│ Email │ │ Sms │ │ WeChat │
│Notification│ │Notification│ │Notification │
└────────────┘ └────────────┘ └──────────────┘


┌────────────────────────┐
│ NotificationFactory │
│ createNotification() │
└──────────▲─────────────┘

┌────────────────┼──────────────────┐
│ │ │
┌──────────────┐ ┌──────────────┐ ┌────────────────┐
│ EmailFactory │ │ SmsFactory │ │ WeChatFactory │
└──────────────┘ └──────────────┘ └────────────────┘

这里的关键点是:

  • 业务代码依赖 Notification 抽象,而不是具体通知类;
  • 客户端可以依赖 NotificationFactory 抽象,而不是具体工厂;
  • 新增产品时,新增具体产品和具体工厂即可;
  • 老代码可以尽量保持不动。

chapter 4:案例背景:通知发送系统

下面用一个贴近 Java 后端开发的案例来讲。

假设我们正在开发一个 SaaS 系统,需要在不同业务场景下发送通知:

  • 用户注册成功后,发送邮件;
  • 登录异常时,发送短信;
  • 审批完成后,发送企业微信消息。

我们希望通知发送逻辑满足几个要求:

  1. 业务层不要直接依赖 EmailNotificationSmsNotification 这些具体类;
  2. 新增通知方式时,尽量不要修改已有代码;
  3. 不同通知方式的初始化逻辑可以独立维护;
  4. 后续可以比较自然地接入 Spring 容器。

chapter 5:定义抽象产品

先定义通知接口。

1
2
3
4
public interface Notification {

void send(String receiver, String content);
}

这个接口就是“抽象产品”。

业务层只需要知道有一个东西可以发送通知,不需要关心它到底是邮件、短信还是企业微信。

chapter 6:定义具体产品

1. 邮件通知

1
2
3
4
5
6
7
public class EmailNotification implements Notification {

@Override
public void send(String receiver, String content) {
System.out.println("发送邮件通知,receiver = " + receiver + ", content = " + content);
}
}

2. 短信通知

1
2
3
4
5
6
7
public class SmsNotification implements Notification {

@Override
public void send(String receiver, String content) {
System.out.println("发送短信通知,receiver = " + receiver + ", content = " + content);
}
}

3. 企业微信通知

1
2
3
4
5
6
7
public class WeChatNotification implements Notification {

@Override
public void send(String receiver, String content) {
System.out.println("发送企业微信通知,receiver = " + receiver + ", content = " + content);
}
}

这几个类都是具体产品,它们实现了统一的 Notification 接口。

chapter 7:定义抽象工厂

接着定义通知工厂接口。

1
2
3
4
public interface NotificationFactory {

Notification createNotification();
}

这个接口只负责一件事:创建通知对象。

注意,这里没有传入 type 字符串,也没有写 if else

因为工厂方法模式的重点就是:

让具体工厂决定创建哪一种具体产品。

chapter 8:定义具体工厂

1. 邮件通知工厂

1
2
3
4
5
6
7
public class EmailNotificationFactory implements NotificationFactory {

@Override
public Notification createNotification() {
return new EmailNotification();
}
}

2. 短信通知工厂

1
2
3
4
5
6
7
public class SmsNotificationFactory implements NotificationFactory {

@Override
public Notification createNotification() {
return new SmsNotification();
}
}

3. 企业微信通知工厂

1
2
3
4
5
6
7
public class WeChatNotificationFactory implements NotificationFactory {

@Override
public Notification createNotification() {
return new WeChatNotification();
}
}

到这里,工厂方法模式的基本结构就出来了。

一个具体工厂,对应一种具体产品。

chapter 9:客户端调用

业务代码可以这样使用:

1
2
3
4
5
6
7
8
9
10
public class NotificationClient {

public static void main(String[] args) {
NotificationFactory factory = new EmailNotificationFactory();

Notification notification = factory.createNotification();

notification.send("user@example.com", "欢迎注册系统");
}
}

这段代码里,业务代码依赖的是:

  • NotificationFactory
  • Notification

也就是依赖抽象,而不是依赖具体实现。

如果要换成短信通知:

1
2
3
4
5
6
7
8
9
10
public class NotificationClient {

public static void main(String[] args) {
NotificationFactory factory = new SmsNotificationFactory();

Notification notification = factory.createNotification();

notification.send("13800000000", "您的验证码是 9527");
}
}

虽然这里客户端还需要选择具体工厂,但对象创建逻辑已经被拆分到了不同工厂中。

chapter 10:继续优化:把公共流程放到抽象工厂中

在真实业务里,发送通知往往不是只有 send 一步。

可能会有:

  1. 创建通知对象;
  2. 参数校验;
  3. 发送通知;
  4. 记录日志;
  5. 处理异常;
  6. 保存发送结果。

如果每个客户端都自己写这套流程,就会重复。

这时可以把公共流程放到抽象工厂或者抽象父类中,让子类只负责创建具体产品。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public abstract class AbstractNotificationFactory {

public void sendMessage(String receiver, String content) {
validate(receiver, content);

Notification notification = createNotification();

notification.send(receiver, content);

recordLog(receiver, content);
}

protected abstract Notification createNotification();

private void validate(String receiver, String content) {
if (receiver == null || receiver.isBlank()) {
throw new IllegalArgumentException("receiver can not be blank");
}

if (content == null || content.isBlank()) {
throw new IllegalArgumentException("content can not be blank");
}
}

private void recordLog(String receiver, String content) {
System.out.println("记录通知发送日志,receiver = " + receiver + ", content = " + content);
}
}

然后定义具体工厂。

1
2
3
4
5
6
7
public class EmailNotificationFactory extends AbstractNotificationFactory {

@Override
protected Notification createNotification() {
return new EmailNotification();
}
}
1
2
3
4
5
6
7
public class SmsNotificationFactory extends AbstractNotificationFactory {

@Override
protected Notification createNotification() {
return new SmsNotification();
}
}

客户端调用:

1
2
3
4
5
6
7
8
public class NotificationClient {

public static void main(String[] args) {
AbstractNotificationFactory factory = new EmailNotificationFactory();

factory.sendMessage("user@example.com", "欢迎注册系统");
}
}

这种写法非常常见。

它和模板方法模式经常组合使用:

  • 工厂方法负责创建对象;
  • 模板方法负责固定业务流程;
  • 子类只覆盖变化点。

这也是设计模式有意思的地方:它们不是一个个孤立的小玩具,而是可以组合出真正好用的工程结构。

chapter 11:在 Spring Boot 中如何理解工厂方法模式

在 Spring 项目里,我们很少手写大量 new XxxFactory()

因为 Spring 容器本身就承担了很大一部分“对象创建”和“依赖注入”的职责。

例如:

1
2
3
4
5
6
public interface Notification {

String type();

void send(String receiver, String content);
}

邮件通知实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class EmailNotification implements Notification {

@Override
public String type() {
return "email";
}

@Override
public void send(String receiver, String content) {
System.out.println("发送邮件通知:" + receiver + "," + content);
}
}

短信通知实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class SmsNotification implements Notification {

@Override
public String type() {
return "sms";
}

@Override
public void send(String receiver, String content) {
System.out.println("发送短信通知:" + receiver + "," + content);
}
}

企业微信通知实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class WeChatNotification implements Notification {

@Override
public String type() {
return "wechat";
}

@Override
public void send(String receiver, String content) {
System.out.println("发送企业微信通知:" + receiver + "," + content);
}
}

然后可以定义一个通知路由器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class NotificationRouter {

private final Map<String, Notification> notificationMap;

public NotificationRouter(List<Notification> notifications) {
this.notificationMap = notifications.stream()
.collect(Collectors.toUnmodifiableMap(Notification::type, notification -> notification));
}

public Notification getNotification(String type) {
Notification notification = notificationMap.get(type);

if (notification == null) {
throw new IllegalArgumentException("Unsupported notification type: " + type);
}

return notification;
}
}

业务服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class NotificationService {

private final NotificationRouter notificationRouter;

public NotificationService(NotificationRouter notificationRouter) {
this.notificationRouter = notificationRouter;
}

public void send(String type, String receiver, String content) {
Notification notification = notificationRouter.getNotification(type);

notification.send(receiver, content);
}
}

严格来说,这个写法更像是:

  • 策略模式;
  • 简单工厂;
  • Spring Bean 容器自动装配;
  • 工厂思想的工程化落地。

它不完全是传统教科书里的工厂方法模式,但在真实 Spring Boot 项目中非常常见,也更实用。

不要为了“像设计模式”而牺牲工程简洁性。设计模式是菜刀,不是仪式用品。

chapter 12:工厂方法模式和简单工厂模式的区别

对比项 简单工厂模式 工厂方法模式
是否属于 GoF 经典 23 种设计模式
创建逻辑放在哪里 一个统一工厂类 多个具体工厂类
新增产品时 通常要修改工厂类 新增产品类和工厂类
是否符合开闭原则 较弱 较强
类数量
复杂度 中等
适合场景 产品少、变化少 产品多、扩展频繁
典型问题 工厂类膨胀 类数量增加

简单工厂更像是“先把创建逻辑收口”。

工厂方法更像是“让创建逻辑也具备扩展能力”。

所以不是说工厂方法一定比简单工厂高级,而是它们适合的场景不同。

如果系统里只有两三种对象,并且长期不会变,简单工厂足够了。

如果产品类型很多,并且经常新增,工厂方法更合适。

chapter 13:工厂方法模式的优点

1. 符合开闭原则

新增产品时,可以新增具体产品类和具体工厂类,而不是修改原来的工厂类。

2. 降低客户端与具体产品的耦合

客户端依赖抽象产品和抽象工厂,不直接依赖具体产品。

3. 单一职责更清晰

每个具体工厂只负责创建一种产品,对象创建逻辑更加分散和清晰。

4. 扩展性更好

当产品创建逻辑比较复杂时,例如要加载配置、初始化连接、选择不同参数,放在独立工厂中会更容易维护。

chapter 14:工厂方法模式的缺点

1. 类数量增加

每新增一个产品,通常就要新增一个具体产品类和一个具体工厂类。

如果产品本身很简单,这种写法会显得“过度设计”。

2. 客户端仍然需要选择具体工厂

传统工厂方法模式下,客户端还是要知道使用哪个具体工厂。

这时通常需要配合配置文件、依赖注入、枚举路由或者 Spring 容器来进一步解耦。

3. 不适合产品族创建

如果要创建一组相关对象,比如:

  • MySQL Connection、MySQL Command、MySQL Transaction;
  • PostgreSQL Connection、PostgreSQL Command、PostgreSQL Transaction;

这种情况更适合抽象工厂模式,而不是普通工厂方法模式。

chapter 15:适用场景

工厂方法模式适合以下场景。

1. 产品类型较多,并且经常扩展

例如:

  • 多种支付渠道;
  • 多种通知渠道;
  • 多种文件解析器;
  • 多种导出器;
  • 多种订单处理器;
  • 多种三方平台适配器。

2. 创建对象的逻辑比较复杂

例如创建对象前需要:

  • 读取配置;
  • 设置认证信息;
  • 初始化客户端;
  • 注入不同策略;
  • 根据环境创建不同实现;
  • 做连接池或者资源准备。

3. 希望业务层依赖抽象,而不是依赖具体类

例如业务代码不应该直接写:

1
AliPayClient client = new AliPayClient();

更合理的是依赖:

1
PaymentClient client = paymentClientFactory.createClient();

这样业务层关注的是支付能力,而不是某个具体支付平台。

4. 框架或插件化系统

如果你的系统希望允许外部模块扩展功能,工厂方法模式就很常见。

例如:

  • 日志框架创建不同 Appender;
  • JDBC 创建不同数据库连接;
  • Spring 创建不同 Bean;
  • ORM 框架创建不同 Dialect;
  • 文件系统适配不同存储实现。

chapter 16:不适合使用的场景

工厂方法模式也不是哪里都要用。

以下场景不建议强行使用:

1. 产品类型很少,而且变化不大

如果系统只有两个实现类,并且几年都不会变,简单工厂或者直接依赖注入就够了。

2. 创建逻辑非常简单

如果只是简单 new User(),没有必要套一层工厂。

设计模式不是为了让代码看起来“高级”,而是为了让变化更容易管理。

3. 使用 Spring 后已经能自然解耦

在 Spring Boot 项目中,如果通过接口、@Component@AutowiredMap<String, Bean> 已经能很好解决问题,就不用再硬套传统工厂类。

不然代码会从“优雅解耦”变成“设计模式 cosplay”。

chapter 17:真实项目中的实践建议

1. 先识别变化点

使用工厂方法之前,先问自己:

这里真正变化的是对象类型,还是对象内部的算法行为?

如果变化的是行为策略,可能更适合策略模式。

如果变化的是对象创建过程,工厂方法才更合适。

2. 不要迷信模式名称

在真实项目里,很多代码并不是纯粹的某一种模式,而是几种思想组合:

  • 工厂方法 + 模板方法;
  • 工厂 + 策略;
  • 工厂 + 依赖注入;
  • 工厂 + 配置中心;
  • 工厂 + SPI 插件机制。

工程上真正重要的是:代码是否更清晰、更稳定、更容易扩展。

3. Spring 项目优先使用接口 + Bean 管理

在 Spring Boot 中,很多时候可以把具体实现交给 Spring 管理,然后通过 Map<String, Interface>List<Interface> 实现路由。

这比手写大量 new XxxFactory() 更自然。

4. 工厂中不要塞业务流程

工厂主要负责创建对象,不要把大量业务逻辑写进去。

如果工厂类里开始出现订单校验、库存扣减、事务处理、消息发送,那它就不是工厂了,它已经变成“业务大杂烩”。锅很大,但这锅不该它背。

5. 可以配合枚举或配置中心使用

例如:

1
2
3
4
5
6
public enum NotificationType {

EMAIL,
SMS,
WECHAT
}

或者通过配置中心控制使用哪个具体实现:

1
2
notification:
default-type: email

然后再由配置驱动工厂选择具体实现。

chapter 18:完整案例代码汇总

抽象产品

1
2
3
4
public interface Notification {

void send(String receiver, String content);
}

具体产品

1
2
3
4
5
6
7
public class EmailNotification implements Notification {

@Override
public void send(String receiver, String content) {
System.out.println("发送邮件通知,receiver = " + receiver + ", content = " + content);
}
}
1
2
3
4
5
6
7
public class SmsNotification implements Notification {

@Override
public void send(String receiver, String content) {
System.out.println("发送短信通知,receiver = " + receiver + ", content = " + content);
}
}
1
2
3
4
5
6
7
public class WeChatNotification implements Notification {

@Override
public void send(String receiver, String content) {
System.out.println("发送企业微信通知,receiver = " + receiver + ", content = " + content);
}
}

抽象工厂

1
2
3
4
public interface NotificationFactory {

Notification createNotification();
}

具体工厂

1
2
3
4
5
6
7
public class EmailNotificationFactory implements NotificationFactory {

@Override
public Notification createNotification() {
return new EmailNotification();
}
}
1
2
3
4
5
6
7
public class SmsNotificationFactory implements NotificationFactory {

@Override
public Notification createNotification() {
return new SmsNotification();
}
}
1
2
3
4
5
6
7
public class WeChatNotificationFactory implements NotificationFactory {

@Override
public Notification createNotification() {
return new WeChatNotification();
}
}

客户端

1
2
3
4
5
6
7
8
9
10
public class NotificationClient {

public static void main(String[] args) {
NotificationFactory factory = new EmailNotificationFactory();

Notification notification = factory.createNotification();

notification.send("user@example.com", "欢迎注册系统");
}
}

chapter 19:一句话总结

工厂方法模式的本质是:

把对象创建这件事抽象出来,并交给具体工厂完成,从而让系统在新增产品时尽量扩展新类,而不是修改旧代码。

它比简单工厂更符合开闭原则,但也会带来更多类。

所以使用它的关键不是“看起来像不像设计模式”,而是系统中是否真的存在频繁变化的对象创建逻辑。

如果有,工厂方法模式很香。

如果没有,别硬上,代码会油。

参考资料

  • Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software.
  • Robert C. Martin. Agile Software Development, Principles, Patterns, and Practices.
  • Joshua Bloch. Effective Java.
  • Spring Framework Documentation: Core Technologies - The IoC Container.
  • Refactoring Guru: Factory Method Pattern.
  • SourceMaking: Factory Method Design Pattern.

启示录

富贵岂由人,时会高志须酬。

能成功于千载者,必以近察远。


设计模式:工厂方法模式
https://allendericdalexander.github.io/2026/04/01/java/design/02factory-method-pattern-blog/
作者
AtLuoFu
发布于
2026年4月1日
许可协议