抽象工厂模式从入门到精通:一个多云存储案例的完整剖析(修订版)
本文通过一个真实的多云存储场景,从理论到实践完整讲解抽象工厂模式,包含完整代码示例、设计原则分析、面试高频问题及企业级优化方案。
修订版基于首轮评审反馈,修复了 6 处关键问题,新增了产品等级扩展的深度方案、JDK 真实案例剖析和决策判断流程。
目录
- 写在前面:为什么要学习抽象工厂模式
- 抽象工厂模式理论篇
- 代码实战篇:多云存储案例完整实现
- 设计原则分析篇
- 企业级优化方案
- 产品等级扩展:抽象工厂最大的痛点与深度解法 新增·深度
- JDK 与 Spring 中的抽象工厂真实案例 新增
- 优缺点与适用场景
- 决策判断流程:到底该不该用抽象工厂 新增
- 面试高频问题及深度解答
- 实战技巧与最佳实践
- 总结与延伸
一、写在前面:为什么要学习抽象工厂模式?
1.1 一个真实的业务痛点
假设你在开发一个支持多云架构的系统,需要对接阿里云、AWS、腾讯云等多家云服务商。每家云服务商都提供一系列服务:
| 云厂商 | 存储服务 | 队列服务 | 计算服务 |
|---|---|---|---|
| 阿里云 | OSS | MNS | ECS |
| AWS | S3 | SQS | EC2 |
| 腾讯云 | COS | CMQ | CVM |
核心需求:切换云厂商时,所有服务必须一起切换。比如从阿里云切换到AWS,存储要换成S3,队列要换成SQS,计算要换成EC2。
如果不用设计模式,代码会变成这样:
arduino
// ❌ 没有使用设计模式的糟糕代码
public class CloudService {
private StorageService storage;
private QueueService queue;
public void useAliyun() {
storage = new AliyunOssStorage(); // 硬编码
queue = new AliyunMnsQueue(); // 硬编码
}
public void useAws() {
storage = new AwsS3Storage(); // 硬编码
queue = new AwsSqsQueue(); // 硬编码
}
}
这段代码有什么问题?
- 违反开闭原则:新增腾讯云需要修改现有代码
- 耦合度高:客户端依赖具体实现类
- 维护困难:每次新增服务类型(如监控服务),所有切换方法都要改
1.2 本文目标
通过本文,你将掌握:
- 抽象工厂模式的核心概念和理论
- 如何用抽象工厂模式解决多云适配问题
- 代码实战与设计原则分析
- 企业级应用的最佳实践
- 产品等级扩展的深度解法(新增)
- JDK / Spring 框架中的真实案例剖析(新增)
- 面试高频问题及解答
二、抽象工厂模式理论篇
2.1 什么是抽象工厂模式?
定义: 抽象工厂模式(Abstract Factory Pattern)提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
通俗理解: 抽象工厂是一个"超级工厂",它管理着一组相关产品的创建。就像汽车制造厂,同一个工厂(如丰田)生产的发动机、轮胎、座椅都是配套的;而不同工厂(如丰田 vs 本田)生产的是两套不同的产品族。
2.2 核心概念解释
概念一:产品族(Product Family)
指属于同一厂商或同一体系的一组产品。
objectivec
产品族 = 同一家工厂生产的所有产品
阿里云产品族 = {OSS存储, MNS队列, ECS计算}
AWS产品族 = {S3存储, SQS队列, EC2计算}
腾讯云产品族 = {COS存储, CMQ队列, CVM计算}
概念二:产品等级(Product Hierarchy)
指同一类型产品的不同实现。
objectivec
产品等级 = 同一种产品在不同厂商的实现
存储产品等级 = {OSS, S3, COS} → 都是存储服务
队列产品等级 = {MNS, SQS, CMQ} → 都是队列服务
关键理解:
- 产品族是横向的(同一厂商的多个产品)
- 产品等级是纵向的(同类型产品的不同厂商实现)
- 抽象工厂保证:同一产品族的产品一起使用
2.3 模式结构
scss
┌────────────────────────────────────────────────────────────┐
│ <<interface>> │
│ CloudFactory │
│ + createStorage(): StorageService │
│ + createQueue(): QueueService │
└──────────┬──────────────────────────┬─────────────────────┘
│ │
│ implements │ implements
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ AliyunFactory │ │ AwsFactory │
│ + createStorage() │ │ + createStorage() │
│ + createQueue() │ │ + createQueue() │
└──────────┬──────────┘ └──────────┬──────────┘
│ │
│ creates │ creates
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ <<interface>> │ │ <<interface>> │
│ StorageService │ │ QueueService │
└──────────┬──────────┘ └──────────┬──────────┘
┌─────┴─────┐ ┌─────┴─────┐
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│AliyunOSS│ │AwsS3 │ │AliyunMNS│ │AwsSQS │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
↑ ↑ ↑ ↑
└───────────┴───────────────┴───────────┘
│
┌───────┴────────┐
│ Client │ ← 只依赖抽象接口
└────────────────┘
2.4 四个核心角色
| 角色 | 说明 | 本例对应 |
|---|---|---|
| 抽象工厂 | 声明创建产品族中所有产品的接口 | CloudFactory |
| 具体工厂 | 实现抽象工厂,为特定产品族创建产品 | AliyunFactory、AwsFactory |
| 抽象产品 | 产品家族的基类/接口 | StorageService、QueueService |
| 具体产品 | 具体工厂创建的具体产品实例 | AliyunOssStorage、AwsS3Storage等 |
2.5 抽象工厂 vs 工厂方法
| 对比维度 | 工厂方法模式 | 抽象工厂模式 |
|---|---|---|
| 关注点 | 创建一个产品 | 创建一组产品(产品族) |
| 工厂方法数 | 1个创建方法 | 多个创建方法 |
| 产品类型 | 单一产品等级 | 多个产品等级 |
| 扩展方式 | 新增产品时加新工厂 | 新增产品族时加新工厂 |
| 复杂度 | 简单 | 复杂 |
| 适用场景 | 产品类型单一,但实现多样 | 多个产品需要组合使用 |
记忆口诀:
- 工厂方法:一个工厂造一种产品(如面包厂只造面包)
- 抽象工厂:一个工厂造一套产品(如丰田厂造发动机+轮胎+座椅)
三、代码实战篇:多云存储案例完整实现
3.1 业务场景说明
需求:
- 系统需要支持阿里云、AWS两家云服务商
- 每家云服务商都需要提供:文件存储 + 消息队列
- 切换云服务商时,存储和队列一起切换
- 未来可能扩展支持腾讯云、华为云等
改进点 1:明确文件结构
原文章将所有代码堆在一个
.java文件中,且主类为public,但一个.java文件只能有一个public类------初学者照抄会编译报错。修订版明确标注每个类所属的文件,符合真实项目结构。
3.2 完整代码实现
文件结构
bash
src/main/java/com/devkit/patterns/creational/
├── product/
│ ├── StorageService.java # 抽象产品A
│ ├── QueueService.java # 抽象产品B
│ ├── aliyun/
│ │ ├── AliyunOssStorage.java # 具体产品A1
│ │ └── AliyunMnsQueue.java # 具体产品B1
│ └── aws/
│ ├── AwsS3Storage.java # 具体产品A2
│ └── AwsSqsQueue.java # 具体产品B2
├── factory/
│ ├── CloudFactory.java # 抽象工厂
│ ├── AliyunFactory.java # 具体工厂1
│ └── AwsFactory.java # 具体工厂2
└── AbstractFactoryDemo.java # 客户端入口
第一步:定义抽象产品接口
StorageService.java
typescript
package com.devkit.patterns.creational.product;
/**
* 存储服务接口 - 抽象产品A
*/
public interface StorageService {
/**
* 上传文件
* @param fileName 文件名
* @param content 文件内容
* @return 文件访问地址
*/
String uploadFile(String fileName, String content);
/**
* 下载文件
* @param fileKey 文件标识
* @return 文件内容
*/
String downloadFile(String fileKey);
}
QueueService.java
arduino
package com.devkit.patterns.creational.product;
/**
* 队列服务接口 - 抽象产品B
*/
public interface QueueService {
void sendMessage(String topic, String message);
String receiveMessage(String topic);
}
第二步:实现具体产品 --- 阿里云产品族
AliyunOssStorage.java
typescript
package com.devkit.patterns.creational.product.aliyun;
import com.devkit.patterns.creational.product.StorageService;
/**
* 阿里云OSS存储实现 - 具体产品A1
*/
public class AliyunOssStorage implements StorageService {
private static final String ENDPOINT = "https://oss-cn-hangzhou.aliyuncs.com";
@Override
public String uploadFile(String fileName, String content) {
String fileKey = "aliyun-oss://" + System.currentTimeMillis() + "/" + fileName;
System.out.println("[阿里云OSS] 上传文件: " + fileName + ", 大小: " + content.length() + " bytes");
System.out.println("[阿里云OSS] 存储节点: " + ENDPOINT);
return fileKey;
}
@Override
public String downloadFile(String fileKey) {
System.out.println("[阿里云OSS] 下载文件: " + fileKey);
return "文件内容 from Aliyun OSS...";
}
}
AliyunMnsQueue.java
typescript
package com.devkit.patterns.creational.product.aliyun;
import com.devkit.patterns.creational.product.QueueService;
public class AliyunMnsQueue implements QueueService {
private static final String QUEUE_ENDPOINT = "https://mns.cn-hangzhou.aliyuncs.com";
@Override
public void sendMessage(String topic, String message) {
System.out.println("[阿里云MNS] 发送消息到队列: " + topic + ", 内容: " + message);
System.out.println("[阿里云MNS] 消息已持久化到: " + QUEUE_ENDPOINT);
}
@Override
public String receiveMessage(String topic) {
System.out.println("[阿里云MNS] 从队列接收消息: " + topic);
return "来自阿里云MNS的消息内容";
}
}
第三步:实现具体产品 --- AWS产品族
AwsS3Storage.java
typescript
package com.devkit.patterns.creational.product.aws;
import com.devkit.patterns.creational.product.StorageService;
public class AwsS3Storage implements StorageService {
private static final String REGION = "us-east-1";
@Override
public String uploadFile(String fileName, String content) {
String fileKey = "aws-s3://" + REGION + "/" + System.currentTimeMillis() + "/" + fileName;
System.out.println("[AWS S3] 上传文件: " + fileName + ", 大小: " + content.length() + " bytes");
System.out.println("[AWS S3] 存储区域: " + REGION);
return fileKey;
}
@Override
public String downloadFile(String fileKey) {
System.out.println("[AWS S3] 下载文件: " + fileKey);
return "文件内容 from AWS S3...";
}
}
AwsSqsQueue.java
typescript
package com.devkit.patterns.creational.product.aws;
import com.devkit.patterns.creational.product.QueueService;
public class AwsSqsQueue implements QueueService {
private static final String QUEUE_URL = "https://sqs.us-east-1.amazonaws.com/123456789/";
@Override
public void sendMessage(String topic, String message) {
System.out.println("[AWS SQS] 发送消息到队列: " + topic + ", 内容: " + message);
System.out.println("[AWS SQS] 队列地址: " + QUEUE_URL + topic);
}
@Override
public String receiveMessage(String topic) {
System.out.println("[AWS SQS] 从队列接收消息: " + topic);
return "来自AWS SQS的消息内容";
}
}
第四步:定义抽象工厂接口
CloudFactory.java
csharp
package com.devkit.patterns.creational.factory;
import com.devkit.patterns.creational.product.StorageService;
import com.devkit.patterns.creational.product.QueueService;
/**
* 云工厂接口 - 抽象工厂
* 声明创建产品族中所有产品的接口
*/
public interface CloudFactory {
StorageService createStorage();
QueueService createQueue();
}
第五步:实现具体工厂
AliyunFactory.java
typescript
package com.devkit.patterns.creational.factory;
import com.devkit.patterns.creational.product.StorageService;
import com.devkit.patterns.creational.product.QueueService;
import com.devkit.patterns.creational.product.aliyun.AliyunOssStorage;
import com.devkit.patterns.creational.product.aliyun.AliyunMnsQueue;
public class AliyunFactory implements CloudFactory {
@Override
public StorageService createStorage() {
return new AliyunOssStorage();
}
@Override
public QueueService createQueue() {
return new AliyunMnsQueue();
}
}
AwsFactory.java
typescript
package com.devkit.patterns.creational.factory;
import com.devkit.patterns.creational.product.StorageService;
import com.devkit.patterns.creational.product.QueueService;
import com.devkit.patterns.creational.product.aws.AwsS3Storage;
import com.devkit.patterns.creational.product.aws.AwsSqsQueue;
public class AwsFactory implements CloudFactory {
@Override
public StorageService createStorage() {
return new AwsS3Storage();
}
@Override
public QueueService createQueue() {
return new AwsSqsQueue();
}
}
客户端入口
AbstractFactoryDemo.java
java
package com.devkit.patterns.creational;
import com.devkit.patterns.creational.factory.*;
import com.devkit.patterns.creational.product.*;
public class AbstractFactoryDemo {
public static void main(String[] args) {
// ===== 场景1: 使用阿里云 =====
System.out.println("=== 开始使用阿里云 ===");
CloudFactory aliyunFactory = new AliyunFactory();
StorageService aliyunStorage = aliyunFactory.createStorage();
QueueService aliyunQueue = aliyunFactory.createQueue();
String fileUrl = aliyunStorage.uploadFile("order_report.pdf", "PDF content...");
aliyunQueue.sendMessage("file-uploaded", fileUrl);
System.out.println();
// ===== 场景2: 切换到 AWS =====
System.out.println("=== 切换到 AWS ===");
CloudFactory awsFactory = new AwsFactory();
StorageService awsStorage = awsFactory.createStorage();
QueueService awsQueue = awsFactory.createQueue();
String awsUrl = awsStorage.uploadFile("order_report.pdf", "PDF content...");
awsQueue.sendMessage("file-uploaded", awsUrl);
}
}
3.3 运行结果
less
=== 开始使用阿里云 ===
[阿里云OSS] 上传文件: order_report.pdf, 大小: 14 bytes
[阿里云OSS] 存储节点: https://oss-cn-hangzhou.aliyuncs.com
[阿里云MNS] 发送消息到队列: file-uploaded, 内容: aliyun-oss://1640995200000/order_report.pdf
[阿里云MNS] 消息已持久化到: https://mns.cn-hangzhou.aliyuncs.com
=== 切换到 AWS ===
[AWS S3] 上传文件: order_report.pdf, 大小: 14 bytes
[AWS S3] 存储区域: us-east-1
[AWS SQS] 发送消息到队列: file-uploaded, 内容: aws-s3://us-east-1/1640995200000/order_report.pdf
[AWS SQS] 队列地址: https://sqs.us-east-1.amazonaws.com/123456789/file-uploaded
观察要点: 阿里云场景所有操作都用阿里云服务,AWS场景全部切换到AWS服务。客户端代码几乎一样,只是工厂对象不同。
四、设计原则分析篇
4.1 开闭原则(Open-Closed Principle)
原则: 对扩展开放,对修改关闭。
typescript
// ✅ 新增腾讯云时,完全不需要修改已有代码
public class TencentCosStorage implements StorageService { ... }
public class TencentCmqQueue implements QueueService { ... }
public class TencentFactory implements CloudFactory {
@Override public StorageService createStorage() { return new TencentCosStorage(); }
@Override public QueueService createQueue() { return new TencentCmqQueue(); }
}
// AliyunFactory、AwsFactory 完全不用修改!
注意 :开闭原则只对新增产品族 成立。如果需要新增一个产品类型(如监控服务),则所有工厂接口和实现类都要改。这是抽象工厂最大的局限性,详见第六节。
4.2 依赖倒置原则(Dependency Inversion Principle)
原则: 高层模块不应该依赖低层模块,两者都应该依赖抽象。
kotlin
// ✅ 客户端依赖抽象接口
public class Client {
private CloudFactory factory; // 依赖抽象
private StorageService storage; // 依赖抽象
private QueueService queue; // 依赖抽象
public Client(CloudFactory factory) {
this.factory = factory;
this.storage = factory.createStorage();
this.queue = factory.createQueue();
}
}
// ❌ 错误写法:客户端依赖具体类
public class BadClient {
private AliyunOssStorage storage; // 依赖具体实现
private AliyunMnsQueue queue; // 依赖具体实现
}
4.3 单一职责原则(Single Responsibility Principle)
kotlin
class AliyunOssStorage { } // 只负责阿里云存储的细节
class AliyunMnsQueue { } // 只负责阿里云队列的细节
class AliyunFactory { } // 只负责阿里云产品族的组装
4.4 接口隔离原则(Interface Segregation Principle)
csharp
// ✅ 接口只包含必要的方法
public interface CloudFactory {
StorageService createStorage();
QueueService createQueue();
}
// ❌ 错误:包含了客户端可能不需要的方法
public interface BadCloudFactory {
StorageService createStorage();
QueueService createQueue();
MonitoringService createMonitoring(); // 可能不需要
CDNService createCDN(); // 可能不需要
}
五、企业级优化方案
5.1 方案一:工厂注册表 + 配置驱动
改进点 4:修复线程安全问题
原文章使用
HashMap存储注册表,同时提供registerFactory()方法允许运行时写入。HashMap并发写会导致数据丢失甚至死循环(JDK7 链表成环)。修订版改用ConcurrentHashMap。
typescript
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 云工厂注册表 - 管理所有云厂商
* 线程安全:使用 ConcurrentHashMap 支持运行时动态注册
*/
public class CloudFactoryRegistry {
private static final Map<String, CloudFactory> REGISTRY = new ConcurrentHashMap<>();
static {
REGISTRY.put("aliyun", new AliyunFactory());
REGISTRY.put("aws", new AwsFactory());
REGISTRY.put("tencent", new TencentFactory());
}
public static CloudFactory getFactory(String name) {
CloudFactory factory = REGISTRY.get(name.toLowerCase());
if (factory == null) {
throw new IllegalArgumentException("不支持的云厂商: " + name);
}
return factory;
}
public static void registerFactory(String name, CloudFactory factory) {
REGISTRY.put(name.toLowerCase(), factory);
}
public static Set<String> listProviders() {
return REGISTRY.keySet();
}
}
配置驱动客户端:
ini
public class ConfigDrivenClient {
public static void main(String[] args) {
// 从配置文件读取,如 application.properties: cloud.provider=aliyun
String providerName = System.getProperty("cloud.provider", "aliyun");
CloudFactory factory = CloudFactoryRegistry.getFactory(providerName);
StorageService storage = factory.createStorage();
QueueService queue = factory.createQueue();
storage.uploadFile("config.pdf", "config content...");
queue.sendMessage("config-updated", "config.pdf uploaded");
}
}
5.2 方案二:Spring Boot 集成
typescript
@Configuration
public class CloudConfiguration {
@Value("${cloud.provider:aliyun}")
private String providerName;
@Bean
public CloudFactory cloudFactory() {
return CloudFactoryRegistry.getFactory(providerName);
}
@Bean
public StorageService storageService(CloudFactory factory) {
return factory.createStorage();
}
@Bean
public QueueService queueService(CloudFactory factory) {
return factory.createQueue();
}
}
@Service
public class FileService {
@Autowired
private StorageService storage;
@Autowired
private QueueService queue;
public String uploadAndNotify(String fileName, String content, String topic) {
String url = storage.uploadFile(fileName, content);
queue.sendMessage(topic, url);
return url;
}
}
// application.properties
// cloud.provider=aws // 一行配置切换所有服务(需重启生效)
5.3 方案三:SPI 插件化 + 动态加载
改进点 2:补全 SPI 配置文件
原文章展示了
ServiceLoader.load()代码,但完全遗漏了META-INF/services/配置文件 。没有这个文件,ServiceLoader找不到任何实现类,SPI 机制形同虚设。这是原文章最严重的遗漏之一。修订版完整补上。
第一步:定义 SPI 接口
csharp
public interface CloudProviderSpi extends CloudFactory {
String getProviderName();
String getVersion();
boolean isAvailable();
}
第二步:实现 SPI(阿里云为例,独立 jar 包)
typescript
public class AliyunProviderSpi implements CloudProviderSpi {
@Override
public StorageService createStorage() { return new AliyunOssStorage(); }
@Override
public QueueService createQueue() { return new AliyunMnsQueue(); }
@Override
public String getProviderName() { return "aliyun"; }
@Override
public String getVersion() { return "2.0.0"; }
@Override
public boolean isAvailable() { return true; }
}
第三步:配置 SPI 服务文件(关键!)
⚠️ 这步绝对不能省! 在
src/main/resources/META-INF/services/下创建文件,文件名是 SPI 接口的全限定名,内容是实现类的全限定名。
dart
# 文件路径: src/main/resources/META-INF/services/com.devkit.patterns.creational.factory.CloudProviderSpi
com.devkit.patterns.creational.factory.AliyunProviderSpi
com.devkit.patterns.creational.factory.AwsProviderSpi
com.devkit.patterns.creational.factory.TencentProviderSpi
css
目录结构:
src/main/resources/
└── META-INF/
└── services/
└── com.devkit.patterns.creational.factory.CloudProviderSpi ← 文件名 = 接口全限定名
(文件内容 = 每行一个实现类的全限定名)
第四步:插件管理器
typescript
@Component
public class CloudPluginManager {
private final Map<String, CloudProviderSpi> providers = new ConcurrentHashMap<>();
private volatile CloudProviderSpi currentProvider;
@PostConstruct
public void init() {
// ServiceLoader 通过读取 META-INF/services/ 下的配置文件来发现实现类
ServiceLoader<CloudProviderSpi> loader = ServiceLoader.load(CloudProviderSpi.class);
for (CloudProviderSpi provider : loader) {
providers.put(provider.getProviderName(), provider);
System.out.println("加载云厂商插件: " + provider.getProviderName() + " v" + provider.getVersion());
}
if (!providers.isEmpty()) {
currentProvider = providers.values().iterator().next();
}
}
public synchronized void switchTo(String providerName) {
CloudProviderSpi newProvider = providers.get(providerName);
if (newProvider == null) {
throw new IllegalArgumentException("厂商未注册: " + providerName);
}
if (!newProvider.isAvailable()) {
throw new IllegalStateException("厂商不可用: " + providerName);
}
this.currentProvider = newProvider;
System.out.println("已切换到: " + providerName);
}
public CloudFactory getCurrentFactory() {
if (currentProvider == null) {
throw new IllegalStateException("没有可用的云厂商");
}
return currentProvider;
}
}
5.4 方案对比
| 方案 | 复杂度 | 适用场景 | 运行时切换 | 热部署 | 线程安全 |
|---|---|---|---|---|---|
| 工厂注册表 | 低 | 中小型项目 | 支持(注册即可用) | 否 | 需用 ConcurrentHashMap |
| Spring 集成 | 中 | Spring Boot 项目 | 支持(需重启) | 否 | 容器保证 |
| SPI 插件化 | 高 | 大型平台 / 中间件 | 支持 | 支持 | 需自行保证 |
六、产品等级扩展:抽象工厂最大的痛点与深度解法 新增·深度
为什么单独开一章?
"新增产品族容易,新增产品等级困难"是抽象工厂模式最核心的缺点,也是面试中最常被追问的点。原文章对此一笔带过,三种策略都缺少代码和权衡分析。本章从 4 种策略逐一展开,给出完整代码和诚实的 trade-off。
6.1 问题复现
假设系统已经稳定运行,现在需要新增一个监控服务(MonitoringService):
csharp
// 新增的抽象产品
public interface MonitoringService {
void metric(String name, double value);
}
// 问题来了:CloudFactory 接口需要加方法!
public interface CloudFactory {
StorageService createStorage();
QueueService createQueue();
MonitoringService createMonitoring(); // ← 新增
}
这一改,AliyunFactory、AwsFactory、TencentFactory......所有已有工厂全部编译报错。违反了开闭原则。
6.2 四种应对策略
策略一:Java 8+ Interface Default Method(推荐)
Java 8 引入的 default 方法允许在接口中提供默认实现。新增产品类型时,给接口加一个 default 方法返回空实现或抛异常,已有工厂不需要任何修改。
csharp
// 空实现,作为默认返回
public class NoopMonitoringService implements MonitoringService {
@Override
public void metric(String name, double value) {
// no-op,不打点
}
}
// 修改抽象工厂接口,用 default 方法
public interface CloudFactory {
StorageService createStorage();
QueueService createQueue();
// Java 8+ default 方法:新增产品类型不破坏已有工厂
default MonitoringService createMonitoring() {
return new NoopMonitoringService();
}
}
typescript
// 已有工厂:完全不用改!默认返回 NoopMonitoringService
public class AliyunFactory implements CloudFactory {
// createMonitoring() 不用写,继承 default 实现
}
// 需要监控的厂商:override 即可
public class AwsFactory implements CloudFactory {
@Override
public MonitoringService createMonitoring() {
return new AwsCloudWatchMonitoring(); // AWS CloudWatch
}
}
| 优点 | 缺点 |
|---|---|
| 已有工厂零修改,真正的开闭原则 | 只能向下兼容新增,无法删除已有方法 |
| 语法原生,无需额外框架 | 如果默认实现需要状态,default 方法不适合 |
| 编译期类型安全 | default 方法多了之后接口会变臃肿 |
适用场景:产品类型偶尔新增(1-2次/年),Java 8+ 项目。这是实际项目中最常用的方案。
策略二:泛型工厂 + 反射注册
将工厂接口改为泛型方法,通过反射动态创建任意产品类型。
csharp
public interface CloudFactory {
StorageService createStorage();
QueueService createQueue();
/**
* 泛型创建方法:支持任意产品类型
* 新增产品类型时,只需注册映射,不改接口
*/
<T> T createProduct(Class<T> productType);
}
public class AliyunFactory implements CloudFactory {
private final Map<Class<?>, Supplier<?>> registry = new HashMap<>();
public AliyunFactory() {
// 注册产品映射
registry.put(StorageService.class, AliyunOssStorage::new);
registry.put(QueueService.class, AliyunMnsQueue::new);
// 未来新增:只需加一行注册
// registry.put(MonitoringService.class, AliyunCloudMonitor::new);
}
@Override
@SuppressWarnings("unchecked")
public <T> T createProduct(Class<T> productType) {
Supplier<?> supplier = registry.get(productType);
if (supplier == null) {
throw new IllegalArgumentException("不支持的产品类型: " + productType.getSimpleName());
}
return (T) supplier.get();
}
@Override
public StorageService createStorage() {
return createProduct(StorageService.class);
}
@Override
public QueueService createQueue() {
return createProduct(QueueService.class);
}
}
客户端使用:
ini
CloudFactory factory = new AliyunFactory();
MonitoringService monitoring = factory.createProduct(MonitoringService.class);
| 优点 | 缺点 |
|---|---|
| 接口稳定,新增产品类型不改接口 | 丢失编译期类型检查(泛型擦除) |
| 工厂内部注册式扩展,灵活性高 | 代码可读性下降,IDE 跳转不友好 |
| 可结合配置文件动态注册 | 运行时才发现不支持的类型(而非编译期) |
适用场景:产品类型频繁变化,且团队接受用反射换取灵活性。
策略三:组合 + 建造者模式(桥接思路)
将产品创建逻辑从工厂中拆出,用独立的 Builder 组装。每个产品类型有独立的创建策略,工厂只负责"选哪套策略"。
kotlin
// 产品创建策略:每个产品类型一个独立的函数式接口
@FunctionalInterface
public interface StorageCreator { StorageService create(); }
@FunctionalInterface
public interface QueueCreator { QueueService create(); }
@FunctionalInterface
public interface MonitoringCreator { MonitoringService create(); }
// 工厂通过组合持有创建策略,而非硬编码 new
public class CloudFactoryV2 {
private final StorageCreator storageCreator;
private final QueueCreator queueCreator;
private MonitoringCreator monitoringCreator; // 可选,新增产品类型不破坏已有
public CloudFactoryV2(StorageCreator sc, QueueCreator qc) {
this.storageCreator = sc;
this.queueCreator = qc;
}
public CloudFactoryV2 withMonitoring(MonitoringCreator mc) {
this.monitoringCreator = mc;
return this;
}
public StorageService createStorage() { return storageCreator.create(); }
public QueueService createQueue() { return queueCreator.create(); }
public MonitoringService createMonitoring() {
if (monitoringCreator == null) return new NoopMonitoringService();
return monitoringCreator.create();
}
}
java
// 使用:用 Lambda 组装工厂
CloudFactoryV2 aliyun = new CloudFactoryV2(
AliyunOssStorage::new,
AliyunMnsQueue::new
).withMonitoring(AliyunCloudMonitor::new);
CloudFactoryV2 aws = new CloudFactoryV2(
AwsS3Storage::new,
AwsSqsQueue::new
// 不调 withMonitoring,默认 Noop
);
| 优点 | 缺点 |
|---|---|
| 新增产品类型完全不影响已有工厂 | 失去了"接口契约"的约束力 |
| 每个产品类型可独立选择是否支持 | 客户端需要知道有哪些创建方法 |
| Lambda 语法简洁,适合函数式风格 | 不再是经典抽象工厂结构,团队需要理解成本 |
适用场景:产品类型多且各厂商支持情况不同(有的厂商有CDN,有的没有)。
策略四:SPI 插件化(参见 5.3 节)
SPI 本质上也是解决扩展问题------把工厂实现放到独立 jar 中,通过 META-INF/services 配置发现。新增产品类型时,只需在新版本的插件 jar 中增加实现,不影响主程序。
适用场景:大型平台型项目,需要支持第三方厂商以插件形式接入。
6.3 四种策略对比总结
| 策略 | 接口改动 | 已有工厂改动 | 类型安全 | 复杂度 | 推荐度 |
|---|---|---|---|---|---|
| Default Method | 加 default 方法 | 无(按需 override) | 编译期 | 低 | ⭐⭐⭐⭐⭐ |
| 泛型 + 反射 | 不改 | 加注册映射 | 运行时 | 中 | ⭐⭐⭐ |
| 组合 + Builder | 不改(加方法) | 无 | 编译期 | 中 | ⭐⭐⭐⭐ |
| SPI 插件化 | 不改 | 无(新 jar) | 运行时 | 高 | ⭐⭐⭐⭐ |
实战建议:90% 的项目用**策略一(Default Method)**就够了。产品类型不会天天变,一年加一两次,default 方法完全够用且最简单。不要一上来就上 SPI 插件化------那是给 Dubbo、SLF4J 这种框架级项目准备的。
七、JDK 与 Spring 中的抽象工厂真实案例 新增
面试高频追问:"你能在 JDK 或 Spring 中找到抽象工厂的真实应用吗?"------这是区分"背了八股文"和"真正理解了"的分水岭。
7.1 JDK 中的抽象工厂
案例一:DocumentBuilderFactory(XML 解析)
ini
// JDK 内置的抽象工厂,创建 XML 解析器产品族
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse("input.xml");
| 角色 | 对应类 |
|---|---|
| 抽象工厂 | DocumentBuilderFactory |
| 具体工厂 | com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl |
| 抽象产品 | DocumentBuilder(解析器) |
| 具体产品 | 对应的 Xerces DocumentBuilder 实现 |
newInstance() 内部用的就是 SPI 机制 ------按顺序查找 META-INF/services/javax.xml.parsers.DocumentBuilderFactory 配置,找不到就用 JDK 内置默认实现。这就是为什么换个 XML 解析器(如换成 DOM4J)只需换 jar 包和配置,不改代码。
案例二:TransformerFactory(XML 转换)
ini
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.transform(new DOMSource(doc), new StreamResult(outputFile));
结构和 DocumentBuilderFactory 完全一致,也是 SPI 驱动的抽象工厂。
案例三:java.sql.Connection(数据库连接)
这个更巧妙------Connection 本身就是一个抽象工厂,它创建的是一组数据库相关的产品:
ini
Connection conn = DriverManager.getConnection(url, user, pwd);
// Connection 作为抽象工厂,创建一组相关产品
Statement stmt = conn.createStatement(); // 产品A
PreparedStatement ps = conn.prepareStatement(sql); // 产品B(同族变体)
CallableStatement cs = conn.prepareCall(sql); // 产品C(同族变体)
| 角色 | 对应 |
|---|---|
| 抽象工厂 | java.sql.Connection(接口) |
| 具体工厂 | MySQL Connection / Oracle Connection / PG Connection |
| 抽象产品 | Statement / PreparedStatement / CallableStatement |
| 具体产品 | MySQL 的 Statement / Oracle 的 Statement ... |
| 产品族一致性 | MySQL Connection 创建的一定是 MySQL 的 Statement,不会混搭 |
切换数据库(MySQL → PostgreSQL),所有产品一起切换------这就是抽象工厂的核心价值。
7.2 Spring 中的抽象工厂
案例一:BeanFactory / ApplicationContext
Spring 的 BeanFactory 本身就是抽象工厂的极致体现------它创建和管理的是一整套 Bean 产品族。
ini
// BeanFactory = 抽象工厂
BeanFactory factory = new ClassPathXmlApplicationContext("beans.xml");
// 工厂创建产品(Bean)
UserService userService = factory.getBean(UserService.class);
OrderService orderService = factory.getBean(OrderService.class);
| 角色 | 对应 |
|---|---|
| 抽象工厂 | BeanFactory(接口) |
| 具体工厂 | ClassPathXmlApplicationContext / AnnotationConfigApplicationContext |
| 抽象产品 | Object(所有 Bean 的基类) |
| 具体产品 | 各业务 Bean(UserService、OrderService 等) |
案例二:FactoryBean(工厂方法模式的变体)
java
@Component
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory> {
@Override
public SqlSessionFactory getObject() throws Exception {
// 复杂的创建逻辑封装在这里
return new SqlSessionFactoryBuilder().build(configuration);
}
@Override
public Class<?> getObjectType() {
return SqlSessionFactory.class;
}
}
当多个 FactoryBean 协同工作(如 MyBatis 的 SqlSessionFactoryBean + SqlSessionTemplateBean + MapperFactoryBean),它们共同构成了一个 MyBatis 产品族的抽象工厂体系。
案例三:@Configuration + @Bean
kotlin
@Configuration
public class DataSourceConfig {
@Bean("hikariDataSource")
@ConditionalOnProperty(name = "pool.type", havingValue = "hikari")
public DataSource hikari() { return new HikariDataSource(); }
@Bean("druidDataSource")
@ConditionalOnProperty(name = "pool.type", havingValue = "druid")
public DataSource druid() { return new DruidDataSource(); }
}
这里 @Configuration 类就是具体工厂,@Bean 方法就是创建产品的方法,@ConditionalOnProperty 实现了配置驱动的工厂切换。Spring Boot AutoConfiguration 就是建立在这个模式之上的。
八、优缺点与适用场景
8.1 优点
| 优点 | 说明 |
|---|---|
| 产品族一致性 | 保证同一工厂的产品互相配套(AliyunFactory 确保 OSS+MNS 配套) |
| 开闭原则(针对产品族) | 新增产品族不改已有代码(新增腾讯云只需新增类) |
| 依赖倒置 | 客户端依赖抽象不依赖具体 |
| 切换方便 | 换工厂对象即切换整个产品族 |
8.2 缺点
| 缺点 | 说明 | 应对策略 |
|---|---|---|
| 扩展产品等级困难 | 新增产品类型需改所有工厂接口 | Default Method / 组合 / SPI(详见第六节) |
| 类爆炸 | 产品族 × 产品类型 = 类数量 | 用反射或 SPI 动态加载 |
| 过度设计 | 产品族少时增加复杂度 | 评估场景,不盲目使用 |
8.3 经典应用案例
- 跨平台 UI 组件:Windows/Mac/Linux 各一套控件(按钮、文本框、滚动条)
- 云服务 SDK:AWS/Azure/阿里云各有全套服务
- 数据库访问层:MySQL/Oracle/PostgreSQL 各一套连接工具
- 游戏角色创建:不同种族的战士/法师/弓箭手
- JDK XML 解析:DocumentBuilderFactory / TransformerFactory
- JDBC:Connection 创建同族 Statement 产品
九、决策判断流程:到底该不该用抽象工厂 新增
为什么需要这个?
原文章列了一堆 ✅❌ 场景,但没有给一个决策流程。实际项目中,最该问的不是"抽象工厂好不好",而是"我到底该不该用"。以下是基于实战经验的决策判断流程。
一句话核心判断标准
你的产品类型是否稳定?产品族是否会持续增长?
产品类型稳定 + 产品族会增长 → 用抽象工厂
产品类型频繁变化 → 抽象工厂是噩梦,考虑 Builder 或组合模式
只有一个产品类型 → 退化为工厂方法即可,别过度设计
决策流程图
步骤 1:你是否有多个产品类型需要组合使用?
- 否 → 用简单工厂 或工厂方法即可,不要过度设计。
- 是 → 继续 ↓
步骤 2:这些产品是否需要保证来自同一产品族(配套使用)?
- 否 → 各产品独立用工厂方法创建即可。
- 是 → 继续 ↓
步骤 3:产品类型(Product Hierarchy)是否稳定,很少新增?
- 否(频繁新增)→ 考虑组合 + Builder模式,而非抽象工厂。
- 是(基本固定)→ 继续 ↓
步骤 4:产品族(Product Family)是否会持续增长(新增厂商)?
- 否(就 2-3 个)→ 评估是否真的需要工厂,也许直接 new + 配置就够。
- 是 → 继续 ↓
步骤 5:是否需要运行时动态加载第三方厂商?
- 否 → 抽象工厂 + 工厂注册表(方案一)
- 是 → 抽象工厂 + SPI 插件化(方案三)
三种创建型模式的决策对照
| 场景特征 | 推荐模式 | 原因 |
|---|---|---|
| 1 种产品,多种实现 | 工厂方法 | 简单直接,不需要产品族概念 |
| 多种产品,需要配套,产品类型稳定 | 抽象工厂 | 保证产品族一致性,扩展厂商容易 |
| 多种产品,需要配套,产品类型频繁变化 | 组合 + Builder | 避免改接口的开闭原则违反 |
| 复杂对象,分步构建 | 建造者模式 | 关注构建过程而非产品族 |
| 需要复制已有对象 | 原型模式 | 关注克隆而非创建 |
十、面试高频问题及深度解答
Q1: 抽象工厂模式和工厂方法模式有什么区别?
答: 关键区别在于创建目标的数量:
| 方面 | 工厂方法 | 抽象工厂 |
|---|---|---|
| 创建目标 | 创建单一产品 | 创建一组产品(产品族) |
| 工厂接口 | 1个创建方法 | 多个创建方法 |
| 产品类型 | 1个产品等级 | 多个产品等级 |
| 扩展方式 | 扩展产品时加新工厂 | 扩展产品族时加新工厂 |
举例:工厂方法------LoggerFactory 只创建 Logger;抽象工厂------CloudFactory 创建 Storage + Queue + Compute。
Q2: 抽象工厂模式如何应对新增产品类型?
答: 这是抽象工厂的主要缺点。有四种策略(详见第六节):
- Default Method(推荐) :Java 8+ 接口默认方法,已有工厂零修改
- 泛型 + 反射 :
<T> T createProduct(Class<T>),接口不改但丢失编译期检查 - 组合 + Builder:拆分创建策略,用 Lambda 组装
- SPI 插件化 :独立 jar +
META-INF/services配置
90% 的项目用 Default Method 就够了。
Q3: 抽象工厂模式如何避免类爆炸?
答:
- 使用原型模式:预置原型对象,克隆代替 new
- 使用反射:通过类名字符串动态创建
- 使用SPI:插件化加载,按需使用
- 使用组合:减少继承层级
Q4: Spring 框架中哪里体现了抽象工厂模式?
答:
BeanFactory/ApplicationContext本身就是抽象工厂------创建和管理一整套 Bean 产品族FactoryBean接口是工厂方法模式的体现,多个 FactoryBean 协同构成抽象工厂@Configuration+@Bean注解组合就是抽象工厂的注解化形式- Spring Boot AutoConfiguration 中的
@ConditionalOnProperty实现了配置驱动的工厂切换 JdbcTemplate等模板类内部使用了工厂模式创建连接
改进点 5:补充 JDK 真实案例
原文章只提了 Spring,遗漏了 JDK 中最经典的案例。面试官更想听到 JDK 的例子------因为这说明你不只是背了框架文档,而是真正理解了模式本身。
加分回答:JDK 中也有大量抽象工厂应用
javax.xml.parsers.DocumentBuilderFactory:创建 XML 解析器产品族,SPI 驱动javax.xml.transform.TransformerFactory:创建 XML 转换器java.sql.Connection:本身是抽象工厂,创建 Statement / PreparedStatement / CallableStatement 产品族
Q5: 抽象工厂模式的性能考量?
答:
- 对象创建开销:每次创建新对象可能有性能开销 → 可以用对象池/缓存
- 类加载开销:类爆炸导致类加载慢 → 按需加载,延迟初始化
- 内存占用:产品族多时占用内存 → 使用原型模式共享对象
- 线程安全 :工厂通常是单例,注意线程安全问题。注册表用
ConcurrentHashMap,缓存字段用volatile
十一、实战技巧与最佳实践
11.1 命名规范
kotlin
// ✅ 推荐命名
interface CloudFactory { } // 抽象工厂:领域名 + Factory
class AliyunFactory { } // 具体工厂:厂商名 + Factory
interface StorageService { } // 抽象产品:功能名 + Service
class AliyunOssStorage { } // 具体产品:厂商 + 产品名 + 功能
// ❌ 不推荐
interface Factory { } // 太泛
class A { } // 无意义
11.2 异常处理
typescript
public class AliyunFactory implements CloudFactory {
@Override
public StorageService createStorage() {
try {
validateConfig();
return new AliyunOssStorage();
} catch (Exception e) {
throw new CloudServiceCreationException("创建阿里云存储失败", e);
}
}
}
11.3 单例工厂
java
public class AliyunFactory implements CloudFactory {
private static final AliyunFactory INSTANCE = new AliyunFactory();
private AliyunFactory() { }
public static AliyunFactory getInstance() { return INSTANCE; }
}
11.4 工厂缓存(线程安全版)
改进点 4(续):修复缓存工厂的可见性问题
原文章的
CachedAwsFactory缓存字段没有volatile。虽然方法用了synchronized,但如果未来有人加非同步的读路径,会导致可见性问题。修订版加上volatile并使用双重检查锁优化性能。
ini
public class CachedAwsFactory implements CloudFactory {
private volatile StorageService cachedStorage; // volatile 保证可见性
private volatile QueueService cachedQueue;
@Override
public StorageService createStorage() {
// 双重检查锁:避免每次都同步
StorageService result = cachedStorage;
if (result == null) {
synchronized (this) {
result = cachedStorage;
if (result == null) {
result = new AwsS3Storage();
cachedStorage = result;
}
}
}
return result;
}
@Override
public QueueService createQueue() {
QueueService result = cachedQueue;
if (result == null) {
synchronized (this) {
result = cachedQueue;
if (result == null) {
result = new AwsSqsQueue();
cachedQueue = result;
}
}
}
return result;
}
}
十二、总结与延伸
12.1 核心要点回顾
markdown
抽象工厂模式本质:
1. 抽象产品定义标准 → StorageService, QueueService
2. 具体产品实现细节 → AliyunOssStorage, AwsS3Storage
3. 抽象工厂定义产品族 → CloudFactory
4. 具体工厂组装产品族 → AliyunFactory, AwsFactory
5. 客户端依赖抽象,切换灵活
6. 最大局限:新增产品等级困难 → 用 Default Method / 组合 / SPI 解决
7. JDK 真实案例:DocumentBuilderFactory, Connection
8. 决策标准:产品类型稳定 + 产品族增长 → 用;产品类型频繁变 → 不用
12.2 与其他创建型模式的关系
| 模式 | 关系 | 说明 |
|---|---|---|
| 工厂方法 | 抽象工厂的基础 | 抽象工厂的创建方法通常用工厂方法实现 |
| 建造者 | 可组合 | 抽象工厂创建对象,建造者创建复杂对象 |
| 原型 | 可组合 | 抽象工厂可以使用原型克隆来创建产品 |
| 单例 | 可组合 | 工厂本身可以是单例 |
12.3 实际项目中的建议
- 先判断是否真的需要:用决策流程(第九节)走一遍,不要拿着锤子找钉子
- 接口设计要合理:抽象工厂的接口应该稳定,不常变化
- 预留扩展点:用 Default Method 为未来产品类型留好扩展空间
- 注意线程安全:注册表用 ConcurrentHashMap,缓存用 volatile + 双重检查锁
- 结合配置使用:产品选择应由配置驱动,而非硬编码
- SPI 配置别忘:用了 ServiceLoader 一定要配 META-INF/services 文件
12.4 修订版改进清单
| # | 问题 | 改进 |
|---|---|---|
| 1 | 代码编译陷阱(多 public 类同文件) | 明确文件结构,每个类标注所属文件 |
| 2 | SPI 缺少 META-INF/services 配置 | 完整补上配置文件示例和目录结构 |
| 3 | 产品等级扩展解法太浅 | 新增第六节,4 种策略 + 完整代码 + trade-off 分析 |
| 4 | 线程安全问题(HashMap / 无 volatile) | 改用 ConcurrentHashMap,缓存加 volatile + 双重检查锁 |
| 5 | 缺少 JDK 真实案例 | 新增第七节,剖析 DocumentBuilderFactory / Connection / BeanFactory |
| 6 | 决策标准不够锐利 | 新增第九节,5 步决策流程 + 三模式对照表 |
希望这篇修订版能帮助你在实际项目中更好地运用抽象工厂模式,也能在面试中从容应对相关问题。如果有任何问题欢迎留言讨论。