设计模式——工厂方法模式(创建型)
摘要
工厂方法模式是一种创建型设计模式,通过定义创建对象的接口,让子类决定实例化哪个类。它包含抽象产品、具体产品、抽象工厂和具体工厂等角色。该模式使类的实例化延迟到子类,具有良好的扩展性和灵活性,适用于多种场景,如Spring框架中的应用。文中还探讨了结合配置中心动态切换消息类型、Spring Boot自动配置与SPI加载工厂等实战示例,以及运行时动态添加SPI实现类的热插拔技术。
1. 工厂方法设计模式定义
工厂方法模式 是一种 创建型设计模式,它通过 定义一个创建对象的接口,让子类决定实例化哪一个类。工厂方法模式使一个类的实例化延迟到其子类。
定义一个用于创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类的实例化推迟到子类中进行。
角色 | 说明 |
Product | 抽象产品类,定义产品的公共接口 |
ConcreteProduct | 具体产品类,实现了抽象产品接口 |
Creator | 抽象工厂类,声明工厂方法 |
ConcreteCreator | 具体工厂类,重写 |
2. 工厂方法设计模式结构
2.1. 工厂方法类图
- Product:抽象产品
- ConcreteProduct:具体产品
- Factory:抽象工厂
- ConcreteFactory:具体工厂
2.2. 工厂方法时序图
3. 工厂方法设计模式实现方式
工厂方法模式的核心是:将对象的创建延迟到子类中去实现,从而达到 “对扩展开放,对修改关闭” 的目的。
3.1. 🧱 实现步骤
- 定义产品接口(Product)
- 创建多个具体产品类(ConcreteProduct)
- 定义工厂抽象类(Creator)
- 创建具体工厂类(ConcreteCreator)实现产品的创建
- 客户端调用:使用工厂创建产品
3.2. ✅ 示例代码实现
3.2.1. 1️⃣ 抽象产品接口
public interface Product {void use();
}
3.2.2. 2️⃣ 具体产品类
public class ConcreteProductA implements Product {@Overridepublic void use() {System.out.println("使用产品 A");}
}public class ConcreteProductB implements Product {@Overridepublic void use() {System.out.println("使用产品 B");}
}
3.2.3. 3️⃣ 抽象工厂类
public abstract class Creator {public abstract Product createProduct();
}
3.2.4. 4️⃣ 具体工厂类
public class ConcreteCreatorA extends Creator {@Overridepublic Product createProduct() {return new ConcreteProductA();}
}public class ConcreteCreatorB extends Creator {@Overridepublic Product createProduct() {return new ConcreteProductB();}
}
3.2.5. 5️⃣ 客户端代码
public class Client {public static void main(String[] args) {Creator creatorA = new ConcreteCreatorA();Product productA = creatorA.createProduct();productA.use();Creator creatorB = new ConcreteCreatorB();Product productB = creatorB.createProduct();productB.use();}
}
3.2.6. 📌 可扩展性体现
如果后续要新增 ConcreteProductC
,只需:
- 新增一个
ConcreteProductC
类 - 新增一个
ConcreteCreatorC
类,实现createProduct()
返回ProductC
- 不修改原有工厂代码,符合开闭原则(OCP)
3.3. ✳️ 工厂方法总结:
比较点 | 简单工厂 | 工厂方法 |
工厂类 | 一个类处理所有产品创建逻辑 | 每个产品对应一个工厂类 |
新增产品时 | 修改工厂逻辑(违反OCP) | 新增产品类和工厂类(符合OCP) |
灵活性 | 较差 | 更强 |
类的数量 | 少 | 多 |
4. 工厂方法设计模式适合场景
4.1. ✅ 适合使用工厂方法模式的场景
场景 | 描述 |
需要创建的对象具有复杂构建逻辑 | 创建过程复杂或需要依赖其他对象时,用工厂方法将其封装。 |
系统中有多个产品族,且产品种类经常扩展 | 每新增一个产品,不希望改动原来的工厂类。符合开闭原则(OCP)。 |
希望客户端代码与具体产品解耦 | 客户端只依赖产品接口,不依赖具体实现类,降低耦合。 |
框架级别开发,预留扩展点给业务方 | 比如 Spring 中的 BeanFactory、MyBatis 的 TypeHandlerFactory。 |
对象生命周期由工厂统一管理 | 方便缓存、单例控制等。 |
例子:
- Spring 框架中使用
FactoryBean
实现 bean 的创建解耦。 - JDBC 的
Connection
、Statement
创建由ConnectionFactory
封装。 - 不同支付渠道(微信、支付宝、银联)通过工厂生成对应
PayHandler
。
4.2. ❌ 不适合使用工厂方法模式的场景
场景 | 原因 |
❌ 产品种类非常少或固定,不需要频繁扩展 | 工厂类和产品类较少,使用工厂方法反而会增加类数量和结构复杂度。 |
❌ 系统结构简单,对象创建逻辑很简单 | 直接 就可以,无需额外的设计模式来封装。 |
❌ 项目初期阶段,功能未稳定,频繁重构 | 工厂方法类结构多,频繁修改成本高。 |
❌ 对性能要求极高的场景 | 反射或额外的工厂逻辑可能有性能损耗,需权衡使用。 |
例子:
- 简单数据封装对象(DTO/VO),使用
new
更直观清晰。 - 稳定的、不会扩展的枚举或常量类,不需要通过工厂方法。
总结:当你面对不断变化的对象创建需求,并希望将变化隔离时,用工厂方法模式;如果结构简单、创建逻辑轻量,直接 new
反而更高效。
5. 工厂方法设计模式实战示例
下面是一个基于 工厂方法模式 的 Spring 项目示例,适合的业务场景是:多渠道消息发送系统,如支持短信(SMS)、邮件(Email)、推送(Push)等多种消息发送方式,且将来可能还会扩展更多类型。消息发送系统:不同类型消息的发送逻辑不同,易于扩展,且客户端无需关心具体实现。
5.1. Spring 工厂方法模式示例
5.1.1. 定义统一接口(产品接口)
public interface MessageSender {void send(String message);
}
5.1.2. 实现具体的产品类(不同的消息发送实现)
@Component
public class EmailMessageSender implements MessageSender {@Overridepublic void send(String message) {System.out.println("发送邮件: " + message);}
}
@Component
public class SmsMessageSender implements MessageSender {@Overridepublic void send(String message) {System.out.println("发送短信: " + message);}
}
5.1.3. 定义抽象工厂类(可选)
public interface MessageSenderFactory {MessageSender createSender();
}
5.1.4. 实现具体工厂类(根据不同产品创建)
@Component("emailFactory")
public class EmailSenderFactory implements MessageSenderFactory {@Autowiredprivate EmailMessageSender sender;@Overridepublic MessageSender createSender() {return sender;}
}
@Component("smsFactory")
public class SmsSenderFactory implements MessageSenderFactory {@Autowiredprivate SmsMessageSender sender;@Overridepublic MessageSender createSender() {return sender;}
}
5.1.5. 客户端代码(注入工厂调用)
@Service
public class NotificationService {// 也可以通过配置动态切换@Resource(name = "emailFactory") private MessageSenderFactory senderFactory;public void notify(String msg) {MessageSender sender = senderFactory.createSender();sender.send(msg);}
}
5.2. ✅ 优点体现(为何使用工厂方法)
优点 | 说明 |
解耦 |
|
易于扩展 | 新增 |
支持 Spring 管理生命周期 | 工厂和产品都可作为 Spring Bean 管理 |
单一职责 | 每个工厂负责一个类型消息的创建,职责清晰 |
5.3. ✅ 可选升级(使用配置中心 + @Value)
你还可以用配置中心动态切换默认的消息类型:
@Value("${message.type}")
private String type; // "email" / "sms"
通过一个总的 MessageSenderFactoryRegistry
统一管理不同的工厂,按类型取出。
6. 工厂方法设计模式思考
6.1. 配置中心动态切换消息类型
6.1.1. 总体结构图
application.yml└─ message.type=emailMessageSender 接口├─ EmailMessageSender└─ SmsMessageSenderMessageSenderFactory 接口├─ EmailSenderFactory└─ SmsSenderFactoryMessageSenderFactoryRegistry(注册所有工厂)
NotificationService(注入 @Value 配置,从 Registry 获取 Sender)
6.1.2. ✅ application.yml 配置
message:type: email
6.1.3. ✅ @Value 注入默认类型
在客户端(如 NotificationService)中使用:
@Value("${message.type}")
private String type; // 注入配置中心的值
6.1.4. ✅ Factory Registry 实现
@Component
public class MessageSenderFactoryRegistry {private final Map<String, MessageSenderFactory> factoryMap = new HashMap<>();@Autowiredpublic MessageSenderFactoryRegistry(List<MessageSenderFactory> factories) {for (MessageSenderFactory factory : factories) {factoryMap.put(factory.getType(), factory); // getType 方法用于标识工厂}}public MessageSenderFactory getFactory(String type) {MessageSenderFactory factory = factoryMap.get(type);if (factory == null) {throw new IllegalArgumentException("不支持的消息类型: " + type);}return factory;}
}
6.1.5. ✅ 各具体工厂类
@Component
public class EmailSenderFactory implements MessageSenderFactory {@Autowiredprivate EmailMessageSender sender;@Overridepublic MessageSender createSender() {return sender;}@Overridepublic String getType() {return "email";}
}
@Component
public class SmsSenderFactory implements MessageSenderFactory {@Autowiredprivate SmsMessageSender sender;@Overridepublic MessageSender createSender() {return sender;}@Overridepublic String getType() {return "sms";}
}
6.1.6. ✅ 客户端使用 Registry + 配置中心
@Service
public class NotificationService {// 配置中心动态切换消息类型@Value("${message.type}")private String type;@Autowiredprivate MessageSenderFactoryRegistry factoryRegistry;public void notify(String msg) {MessageSender sender = factoryRegistry.getFactory(type).createSender();sender.send(msg);}
}
6.1.7. ✅ 动态配置总结
点 | 说明 |
| 注入配置中心的策略标识 |
| 将所有具体工厂注册进来,用于动态选择 |
| 各个工厂自报家门,便于注册 |
使用场景 | 可灵活切换策略(消息类型),并支持后续热扩展 |
6.2. Spring Boot 自动配置 + SPI 加载工厂
以下是基于 Spring Boot 自动配置 + SPI + 工厂注册机制 的完整示例,用于实现插件式、可扩展的策略工厂体系,常用于消息发送、支付方式、风控策略等系统中。
支持在多个 jar 插件中以 SPI 方式注册 MessageSender
实现,并通过 Spring Boot 自动装配到一个注册中心中,客户端使用配置中心动态切换使用哪个策略。
6.2.1. ✅ 项目结构概览
src
├── META-INF
│ └── services
│ └── com.example.spi.MessageSender
├── com.example.spi
│ └── MessageSender.java
├── com.example.impl
│ ├── EmailMessageSender.java
│ └── SmsMessageSender.java
├── com.example.factory
│ ├── MessageSenderFactory.java
│ └── MessageSenderFactoryRegistry.java
├── com.example.config
│ └── MessageSenderAutoConfiguration.java
└── com.example.client└── NotificationService.java
6.2.2. ✳️ 定义 SPI 接口
package com.example.spi;public interface MessageSender {void send(String message);// 用于标识类型,比如 "email"、"sms"String type();
}
6.2.3. ✳️ 实现两个 SPI 类
package com.example.impl;import com.example.spi.MessageSender;public class EmailMessageSender implements MessageSender {public void send(String message) {System.out.println("发送邮件:" + message);}public String type() {return "email";}
}
package com.example.impl;import com.example.spi.MessageSender;public class SmsMessageSender implements MessageSender {public void send(String message) {System.out.println("发送短信:" + message);}public String type() {return "sms";}
}
6.2.4. ✳️ 创建 SPI 配置文件
在 resources/META-INF/services/
目录下创建:
com.example.spi.MessageSender
内容为:
com.example.impl.EmailMessageSender
com.example.impl.SmsMessageSender
6.2.5. ✳️ 创建 Spring 注册工厂类
package com.example.factory;import com.example.spi.MessageSender;
import org.springframework.stereotype.Component;import java.util.*;@Component
public class MessageSenderFactoryRegistry {private final Map<String, MessageSender> senderMap = new HashMap<>();public MessageSenderFactoryRegistry() {// 初始化加载spi接口实现类ServiceLoader<MessageSender> loader = ServiceLoader.load(MessageSender.class);for (MessageSender sender : loader) {senderMap.put(sender.type(), sender);}}public MessageSender getSender(String type) {MessageSender sender = senderMap.get(type);if (sender == null) {throw new IllegalArgumentException("不支持的消息类型: " + type);}return sender;}public Set<String> supportedTypes() {return senderMap.keySet();}
}
6.2.6. ✳️ 自动配置类(可选)
如果你将 SPI 插件打包成独立 jar,可以增加 spring.factories
/ spring.factories
文件,实现自动注册:
resources/META-INF/spring.factories
(Spring Boot 2)或 spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
(Spring Boot 3):
# spring.factories 示例
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.config.MessageSenderAutoConfiguration
package com.example.config;import com.example.factory.MessageSenderFactoryRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MessageSenderAutoConfiguration {@Beanpublic MessageSenderFactoryRegistry messageSenderFactoryRegistry() {return new MessageSenderFactoryRegistry();}
}
6.2.7. ✳️ 使用配置动态切换策略
package com.example.client;import com.example.factory.MessageSenderFactoryRegistry;
import com.example.spi.MessageSender;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;@Service
public class NotificationService {@Value("${message.type}")private String messageType;private final MessageSenderFactoryRegistry registry;public NotificationService(MessageSenderFactoryRegistry registry) {this.registry = registry;}public void notify(String content) {MessageSender sender = registry.getSender(messageType);sender.send(content);}
}
6.2.8. ✳️ application.yml 示例
message:type: email
6.2.9. ✅ 与普通简单工厂对比总结
方式 | 普通简单工厂 | SPI + 自动装配 |
实现方式 | 手动注册、代码维护类型 -> 实例映射 | 自动发现、解耦模块 |
扩展性 | 差,改动原有类 | 高,插件式加载新类型 |
热插拔 | 不支持 | 支持 jar 插件添加 |
Spring 兼容 | 需自己管理 Bean | 可与自动配置整合 |
6.2.10. ✅ 适用场景
- 插件式服务(支付、消息、风控、日志);
- 支持 jar 级别扩展;
- 想在不改主工程代码的情况下支持“新策略”;
- 想结合 Spring 自动装配实现热加载、策略切换。
6.3. ServiceLoader.load(...)
是不是启动加载?
6.3.1. 是不是 Spring 启动时加载?
ServiceLoader.load(...)
不是 Spring 启动时自动加载的 —— 它是懒加载(Lazy Load)机制。
ServiceLoader
是 Java 原生的 SPI 加载器。- 它的加载时机完全取决于你调用
ServiceLoader.load(...)
的代码什么时候执行。 - Spring 启动过程中,不会自动调用
ServiceLoader
来加载服务,除非你显式地调用了它(例如在某个 Bean 初始化时)。
6.3.2. 会不会动态加载 SPI 接口实现类?
会在第一次使用时动态加载,但不是运行时热加载。
ServiceLoader
内部使用懒加载机制:在第一次调用iterator()
或for
循环时才加载并实例化实现类。- 但是这个“动态加载”是指JVM 当前 ClassLoader 中已存在的实现类。
- 不是热加载,不支持运行时新增 jar 或类不重启就生效。
6.3.3. 加载机制原理简要
ServiceLoader.load(Xxx.class)
会从:
classpath:/META-INF/services/your.interface.FullyQualifiedName
去读取该文件,文件内容为接口实现类的全限定类名,每一行一个。
然后使用当前线程的 ClassLoader
反射实例化实现类。
举个例子:
// 手动触发加载(不是 Spring 自动做的)
ServiceLoader<PayChannel> loader = ServiceLoader.load(PayChannel.class);
for (PayChannel channel : loader) {channel.pay();
}
- 只有执行
for (PayChannel channel : loader)
时,才真正实例化每个实现类。 - Spring 本身并不扫描
/META-INF/services
目录。
6.3.4. 如果你想支持运行时动态添加 SPI 实现类(热插拔):
- 需要自定义
ClassLoader
加载外部 Jar。 - 手动调用
ServiceLoader.reload()
。 - Spring 本身不提供这套机制,但可以封装插件框架支持(如 Apache Dubbo、Spring Boot Plugin、OSGi)。
6.4. 运行时动态添加 SPI 实现类(热插拔)示例
如果你希望 在运行时动态添加 SPI 实现类(即热插拔插件机制),Java 原生的 ServiceLoader
默认不支持运行时添加实现类,但你可以通过自定义 ClassLoader
+ SPI 机制 + 热加载逻辑 实现。
要实现 动态 SPI 实现类加载(比如加载一个新 jar 的 SPI 实现),需要以下关键步骤:
6.4.1. 🧩 准备 SPI 接口
例如:
public interface MessageSender {String type();void send(String message);
}
6.4.2. 📁 插件 jar 的 META-INF/services/com.example.MessageSender
中声明实现类
com.example.impl.EmailSender
6.4.3. 🛠️ 构建插件目录结构
plugins/└── message-email.jar
6.4.4. 🧠 自定义 URLClassLoader 加载 jar
public class PluginClassLoader extends URLClassLoader {public PluginClassLoader(URL[] urls, ClassLoader parent) {super(urls, parent);}public static PluginClassLoader loadFrom(String jarPath) throws MalformedURLException {File jarFile = new File(jarPath);return new PluginClassLoader(new URL[]{jarFile.toURI().toURL()}, Thread.currentThread().getContextClassLoader());}
}
6.4.5. 🧪 使用 ServiceLoader + 自定义 ClassLoader 加载插件
public class PluginManager {private final Map<String, MessageSender> senderMap = new ConcurrentHashMap<>();public void loadPlugin(String jarPath) throws Exception {// 自定义 URLClassLoader 加载 jarPluginClassLoader loader = PluginClassLoader.loadFrom(jarPath);ServiceLoader<MessageSender> serviceLoader = ServiceLoader.load(MessageSender.class, loader);for (MessageSender sender : serviceLoader) {senderMap.put(sender.type(), sender);}}public MessageSender getSender(String type) {return senderMap.get(type);}
}
6.4.6. 🔄 动态加载新 jar 插件
PluginManager manager = new PluginManager();
manager.loadPlugin("plugins/message-email.jar");MessageSender sender = manager.getSender("email");
sender.send("Hello Dynamic Plugin!");
6.5. ✅ 支持动态热插拔的关键点
步骤 | 说明 |
| 使用 URLClassLoader 加载外部 jar,和主应用隔离 |
| 指定加载器读取 |
插件隔离 | 保持插件 jar 中依赖与主程序不冲突 |
实例缓存 |
|
SPI 定义 | 每个插件都要放置标准的 |
博文参考
- 2. 工厂方法模式(Factory Method Pattern) — Graphic Design Patterns
- 工厂方法设计模式
- 创建型 - 简单工厂(Simple Factory) | Java 全栈知识体系