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

发布于:2025-06-03 ⋅ 阅读:(16) ⋅ 点赞:(0)

摘要

工厂方法模式是一种创建型设计模式,通过定义创建对象的接口,让子类决定实例化哪个类。它包含抽象产品、具体产品、抽象工厂和具体工厂等角色。该模式使类的实例化延迟到子类,具有良好的扩展性和灵活性,适用于多种场景,如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 {
    @Override
    public void use() {
        System.out.println("使用产品 A");
    }
}

public class ConcreteProductB implements Product {
    @Override
    public 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 {
    @Override
    public Product createProduct() {
        return new ConcreteProductA();
    }
}

public class ConcreteCreatorB extends Creator {
    @Override
    public 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 {
    
    @Override
    public void send(String message) {
        System.out.println("发送邮件: " + message);
    }
}
@Component
public class SmsMessageSender implements MessageSender {
    @Override
    public 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 {
    
    @Autowired
    private EmailMessageSender sender;

    @Override
    public MessageSender createSender() {
        return sender;
    }
}
@Component("smsFactory")
public class SmsSenderFactory implements MessageSenderFactory {
    
    @Autowired
    private SmsMessageSender sender;

    @Override
    public 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=email

MessageSender 接口
  ├─ EmailMessageSender
  └─ SmsMessageSender

MessageSenderFactory 接口
  ├─ EmailSenderFactory
  └─ SmsSenderFactory

MessageSenderFactoryRegistry(注册所有工厂)
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<>();

    @Autowired
    public 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 {
    
    @Autowired
    private EmailMessageSender sender;

    @Override
    public MessageSender createSender() {
        return sender;
    }

    @Override
    public String getType() {
        return "email";
    }
}
@Component
public class SmsSenderFactory implements MessageSenderFactory {
    @Autowired
    private SmsMessageSender sender;

    @Override
    public MessageSender createSender() {
        return sender;
    }

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

6.1.6. ✅ 客户端使用 Registry + 配置中心

@Service
public class NotificationService {
    // 配置中心动态切换消息类型
    @Value("${message.type}")
    private String type;

    @Autowired
    private 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 {

    @Bean
    public 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 加载 jar
        PluginClassLoader 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/<接口名>声明文件

博文参考


网站公告

今日签到

点亮在社区的每一天
去签到