设计模式:工厂方法模式

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

序言

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

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

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

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

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

它的核心思想是:

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

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

正文

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. 业务层不要直接依赖 EmailNotification、SmsNotification 这些具体类;
  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、@Autowired、Map<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日
许可协议