当前位置: 首页 > news >正文

设计模式——工厂方法模式(创建型)

摘要

工厂方法模式是一种创建型设计模式,通过定义创建对象的接口,让子类决定实例化哪个类。它包含抽象产品、具体产品、抽象工厂和具体工厂等角色。该模式使类的实例化延迟到子类,具有良好的扩展性和灵活性,适用于多种场景,如Spring框架中的应用。文中还探讨了结合配置中心动态切换消息类型、Spring Boot自动配置与SPI加载工厂等实战示例,以及运行时动态添加SPI实现类的热插拔技术。

1. 工厂方法设计模式定义

工厂方法模式 是一种 创建型设计模式,它通过 定义一个创建对象的接口,让子类决定实例化哪一个类。工厂方法模式使一个类的实例化延迟到其子类。

定义一个用于创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类的实例化推迟到子类中进行。

角色

说明

Product

抽象产品类,定义产品的公共接口

ConcreteProduct

具体产品类,实现了抽象产品接口

Creator

抽象工厂类,声明工厂方法 createProduct(),返回 Product 类型对象

ConcreteCreator

具体工厂类,重写 createProduct()方法,返回具体的产品对象

2. 工厂方法设计模式结构

2.1. 工厂方法类图

  • Product:抽象产品
  • ConcreteProduct:具体产品
  • Factory:抽象工厂
  • ConcreteFactory:具体工厂

2.2. 工厂方法时序图

3. 工厂方法设计模式实现方式

工厂方法模式的核心是:将对象的创建延迟到子类中去实现,从而达到 “对扩展开放,对修改关闭” 的目的。

3.1. 🧱 实现步骤

  1. 定义产品接口(Product)
  2. 创建多个具体产品类(ConcreteProduct)
  3. 定义工厂抽象类(Creator)
  4. 创建具体工厂类(ConcreteCreator)实现产品的创建
  5. 客户端调用:使用工厂创建产品

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 的 ConnectionStatement 创建由 ConnectionFactory 封装。
  • 不同支付渠道(微信、支付宝、银联)通过工厂生成对应 PayHandler

4.2. ❌ 不适合使用工厂方法模式的场景

场景

原因

产品种类非常少或固定,不需要频繁扩展

工厂类和产品类较少,使用工厂方法反而会增加类数量和结构复杂度。

系统结构简单,对象创建逻辑很简单

直接 new

就可以,无需额外的设计模式来封装。

项目初期阶段,功能未稳定,频繁重构

工厂方法类结构多,频繁修改成本高。

对性能要求极高的场景

反射或额外的工厂逻辑可能有性能损耗,需权衡使用。

例子:

  • 简单数据封装对象(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. ✅ 优点体现(为何使用工厂方法)

优点

说明

解耦

NotificationService只依赖 MessageSenderFactory不关心实现细节

易于扩展

新增 WeChatMessageSender、对应工厂类,无需改动现有代码

支持 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. ✅ 动态配置总结

说明

@Value

注入配置中心的策略标识

MessageSenderFactoryRegistry

将所有具体工厂注册进来,用于动态选择

getType() 方法

各个工厂自报家门,便于注册

使用场景

可灵活切换策略(消息类型),并支持后续热扩展

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. ✅ 支持动态热插拔的关键点

步骤

说明

ClassLoader

使用 URLClassLoader 加载外部 jar,和主应用隔离

ServiceLoader

指定加载器读取 META-INF/services实现

插件隔离

保持插件 jar 中依赖与主程序不冲突

实例缓存

senderMap做好类型-实现缓存映射

SPI 定义

每个插件都要放置标准的 META-INF/services/<接口名>声明文件

博文参考

  • 2. 工厂方法模式(Factory Method Pattern) — Graphic Design Patterns
  • 工厂方法设计模式
  • 创建型 - 简单工厂(Simple Factory) | Java 全栈知识体系

相关文章:

  • RabbitMQ 高级特性
  • Unity 模拟高度尺系统开发详解——实现拖动、范围限制、碰撞吸附与本地坐标轴选择
  • C语言基础(08)【循环结构】
  • PCB设计教程【强化篇】——USB拓展坞原理图设计
  • 生成式AI模型学习笔记
  • Fastapi 学习使用
  • 告别压降损耗与反向电流困扰:汽车电子电源防反接方案全面解析与理想二极管应用
  • 【Unity笔记】Unity WASD+QE 控制角色移动与转向(含 Shift 加速)实现教程
  • 【Python进阶】CPython
  • 分析XSSstrike源码
  • 关联子串 - 华为OD统一考试(JavaScript题解)
  • 姜老师MBTI课程:4条轴线的总结
  • ssh连接断开,保持任务后台执行——tmux
  • Java 中 Redis 过期策略深度解析(含拓展-redis内存淘汰策略列举)
  • spring boot项目中的一些常用提示信息
  • C++17新特性 Lambda表达式
  • 第十四篇:MySQL 运维中的故障场景还原与排查实战技巧
  • NLP基础:从词嵌入到预训练模型应用
  • token
  • 进程间通信(消息队列)
  • 网站建设哪个平台好/专业技能培训机构
  • 吴江网站建设收费/万网官网登录
  • 杭州做网站比较好的公司/seo自学教程
  • 手机网站域名哪里注册时间/搜索百度下载安装
  • 怎么查网站是哪个公司做的/百度推广怎么赚钱
  • 自己怎么做网站视频赚钱/搜索引擎营销的主要方式有