欢迎你来读这篇博客,这篇博客主要是关于工厂方法模式。 其中包括工厂方法模式的核心思想、适用场景、优缺点、与简单工厂模式的区别,以及一个贴近 Java 后端开发的完整案例。
序言 在上一篇简单工厂模式中,我们已经知道:如果系统中存在多种对象创建逻辑,可以把 new 对象的过程统一放到一个工厂类中,避免业务代码到处写 if else 和 new。
但是简单工厂模式有一个很现实的问题:
当产品类型不断增加时,工厂类会越来越胖,每次新增产品都要修改同一个工厂类。
这就违反了设计模式六大原则中的开闭原则 :对扩展开放,对修改关闭。
工厂方法模式正是为了解决这个问题而出现的。
它的核心思想是:
不再由一个统一的大工厂负责创建所有对象,而是定义一个创建对象的工厂接口,让不同的具体工厂负责创建不同的具体产品。
简单说,简单工厂像一个“万能窗口”,什么业务都找它;工厂方法则像“分业务窗口”,支付找支付工厂,通知找通知工厂,导出找导出工厂。万能窗口迟早排长队,分窗口才适合长期扩展。
正文 chapter 1:什么是工厂方法模式 工厂方法模式,英文是 Factory Method Pattern ,属于创建型设计模式。
它定义了一个用于创建对象的接口,但把具体创建哪一个对象的决定权交给子类或者具体工厂类。
经典结构一般包括四个角色:
抽象产品 :定义产品的公共行为。
具体产品 :实现抽象产品接口。
抽象工厂 :定义创建产品的方法。
具体工厂 :实现抽象工厂,负责创建具体产品。
它解决的核心问题不是“如何 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 系统,需要在不同业务场景下发送通知:
用户注册成功后,发送邮件;
登录异常时,发送短信;
审批完成后,发送企业微信消息。
我们希望通知发送逻辑满足几个要求:
业务层不要直接依赖 EmailNotification、SmsNotification 这些具体类;
新增通知方式时,尽量不要修改已有代码;
不同通知方式的初始化逻辑可以独立维护;
后续可以比较自然地接入 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 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.
启示录 富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。