工厂模式
工厂模式用于封装对象创建逻辑。调用方不直接 new 具体类,而是通过工厂获取对象,从而降低调用方和具体实现类之间的耦合。
为什么需要工厂模式
如果业务代码到处写 new AliPayService()、new WechatPayService(),当创建逻辑变化时,所有调用点都要改。
flowchart TD
A["业务代码"] --> B{"支付类型"}
B -- "ali" --> C["new AliPayService"]
B -- "wechat" --> D["new WechatPayService"]
B -- "union" --> E["new UnionPayService"]问题:
- 业务代码知道太多具体类。
- 新增类型时要修改很多 if/else。
- 创建对象前的参数校验、配置读取、客户端初始化会散落各处。
工厂模式把“创建什么对象”集中管理:
flowchart TD
A["业务代码"] --> B["PayFactory"]
B --> C{"根据 type 选择"}
C -- "ali" --> D["AliPayService"]
C -- "wechat" --> E["WechatPayService"]
C -- "union" --> F["UnionPayService"]简单工厂
适合类型少、变化不频繁的场景。
public interface PayService {
void pay(BigDecimal amount);
}public class AliPayService implements PayService {
@Override
public void pay(BigDecimal amount) {
System.out.println("ali pay: " + amount);
}
}public class WechatPayService implements PayService {
@Override
public void pay(BigDecimal amount) {
System.out.println("wechat pay: " + amount);
}
}public class PayFactory {
public static PayService create(String type) {
if ("ali".equals(type)) {
return new AliPayService();
}
if ("wechat".equals(type)) {
return new WechatPayService();
}
throw new IllegalArgumentException("不支持的支付类型: " + type);
}
}调用:
PayService payService = PayFactory.create("ali");
payService.pay(new BigDecimal("100.00"));简单工厂的问题是:新增支付方式时还是要改工厂类。类型很少时可接受;类型经常扩展时,要考虑工厂方法或 Spring Bean Map。
工厂方法
工厂方法把每种产品的创建交给独立工厂。
flowchart TD
A["PayFactory 接口"] --> B["AliPayFactory"]
A --> C["WechatPayFactory"]
B --> D["AliPayService"]
C --> E["WechatPayService"]public interface PayFactory {
PayService create();
}public class AliPayFactory implements PayFactory {
@Override
public PayService create() {
return new AliPayService();
}
}工厂方法适合创建过程复杂、每种产品创建逻辑差异大的场景。但如果只是简单 new,会显得类太多。
Spring 中的工厂写法
Spring 项目中常见写法是让每个实现类成为 Bean,再通过 Map 选择。
商业场景:多支付渠道路由
电商、医疗缴费、SaaS 订阅系统里,支付渠道通常不会只有一种。真实项目里还会出现这些变化:
| 变化点 | 例子 | 如果写死在业务代码里会怎样 |
|---|---|---|
| 渠道新增 | 新增支付宝、微信、银联、医保支付 | 下单、退款、对账、回调多个地方都要改 |
| 渠道配置不同 | AppId、密钥、回调地址、超时时间不同 | 配置读取散落,容易拿错密钥 |
| 渠道能力不同 | 有的支持退款,有的支持分账,有的支持医保结算 | if/else 会扩散到业务主流程 |
| 渠道降级 | 某渠道故障时切换备用渠道 | 业务代码和故障处理耦合 |
比较合理的拆法是:业务层只表达“我要支付”,工厂负责根据订单、租户、渠道类型找到具体支付实现。
flowchart TD
A["订单服务创建支付单"] --> B["读取商户和渠道配置"]
B --> C["PayServiceFactory"]
C --> D{"根据 channel 选择"}
D -- "ALIPAY" --> E["AlipayPayService"]
D -- "WECHAT" --> F["WechatPayService"]
D -- "UNION" --> G["UnionPayService"]
E --> H["返回预支付结果"]
F --> H
G --> H注意:这里的工厂不负责“扣库存、改订单状态、发消息”,这些是业务流程。工厂只负责“找到谁来干活”。
完整 Demo:Spring Bean Map 工厂
支付请求:
public class PayRequest {
private String orderNo;
private String channel;
private BigDecimal amount;
public PayRequest(String orderNo, String channel, BigDecimal amount) {
this.orderNo = orderNo;
this.channel = channel;
this.amount = amount;
}
public String getOrderNo() {
return orderNo;
}
public String getChannel() {
return channel;
}
public BigDecimal getAmount() {
return amount;
}
}统一接口:
public interface PayService {
String channel();
PayResult pay(PayRequest request);
}支付宝实现:
@Component
public class AlipayPayService implements PayService {
@Override
public String channel() {
return "ALIPAY";
}
@Override
public PayResult pay(PayRequest request) {
// 真实项目中这里会组装签名参数、调用支付宝网关、处理响应码
return PayResult.success(request.getOrderNo(), "alipay-prepay-id");
}
}微信实现:
@Component
public class WechatPayService implements PayService {
@Override
public String channel() {
return "WECHAT";
}
@Override
public PayResult pay(PayRequest request) {
return PayResult.success(request.getOrderNo(), "wechat-prepay-id");
}
}工厂:
@Component
public class PayServiceFactory {
private final Map<String, PayService> serviceMap;
public PayServiceFactory(List<PayService> services) {
this.serviceMap = services.stream()
.collect(Collectors.toUnmodifiableMap(PayService::channel, service -> service));
}
public PayService getRequired(String channel) {
PayService service = serviceMap.get(channel);
if (service == null) {
throw new IllegalArgumentException("未配置支付渠道: " + channel);
}
return service;
}
}业务层:
@Service
public class PaymentApplicationService {
private final PayServiceFactory payServiceFactory;
public PaymentApplicationService(PayServiceFactory payServiceFactory) {
this.payServiceFactory = payServiceFactory;
}
public PayResult createPay(PayRequest request) {
PayService payService = payServiceFactory.getRequired(request.getChannel());
return payService.pay(request);
}
}新增医保支付时,只需要新增 MedicalInsurancePayService implements PayService,并返回 MEDICAL_INSURANCE,业务层不用改。
工厂模式到底隔离了什么
工厂模式隔离的不是一行 new,而是对象获取背后的变化:
- 类名变化:调用方不关心具体实现类叫什么。
- 创建参数变化:实现类需要密钥、配置、客户端、连接池时,不污染业务层。
- 实现数量变化:新增实现时尽量只新增类,不修改主流程。
- 生命周期变化:在 Spring 中对象由容器管理,调用方不自己管理单例、依赖和销毁。
- 选择规则变化:可以从配置、租户、订单类型、灰度规则里选择实现。
不用工厂时,业务代码容易从“做业务”变成“判断用哪个类、怎么创建类、怎么初始化类”。这会让业务层同时承担流程编排和对象创建两种职责。
flowchart TD
A["业务层"] --> B{"是否直接 new 具体类"}
B -- "是" --> C["业务层依赖实现细节"]
C --> D["新增渠道要改业务层"]
D --> E["回归风险变高"]
B -- "否" --> F["依赖 PayService 抽象"]
F --> G["工厂集中选择实现"]
G --> H["新增渠道影响面更小"]排查与落地检查
| 问题 | 可能原因 | 排查方式 |
|---|---|---|
| 提示不支持的类型 | 入参 channel 和实现类 channel() 不一致 | 打印工厂注册的 key,检查大小写和枚举值 |
| 启动时报重复 key | 两个实现返回同一个 channel | 检查 Collectors.toMap 异常栈,统一渠道编码 |
| 新增实现没有生效 | 类没有被 Spring 扫描到 | 检查包路径、@Component、启动类扫描范围 |
| 工厂越来越大 | 工厂里写了业务流程 | 把业务逻辑移回 Service 或策略实现 |
| 测试困难 | 直接调用静态工厂 | Spring 项目优先用 Bean 工厂,方便 Mock |
核心原理
工厂模式的原理是把“选择具体实现类”的流程从业务代码中抽离出来,让业务代码只依赖接口。
flowchart TD
A["业务输入 type"] --> B["工厂读取注册表或判断规则"]
B --> C["找到具体实现类"]
C --> D["返回接口类型对象"]
D --> E["业务代码调用接口方法"]这样做的关键不是少写一个 new,而是把变化点集中到工厂或注册表里。调用方只知道 PayService,不需要知道 AliPayService、WechatPayService 的创建细节。
public interface PayService {
String type();
void pay(BigDecimal amount);
}@Component
public class AliPayService implements PayService {
@Override
public String type() {
return "ali";
}
@Override
public void pay(BigDecimal amount) {
System.out.println("ali pay: " + amount);
}
}@Component
public class PayServiceFactory {
private final Map<String, PayService> payServiceMap;
public PayServiceFactory(List<PayService> payServices) {
this.payServiceMap = payServices.stream()
.collect(Collectors.toMap(PayService::type, service -> service));
}
public PayService get(String type) {
PayService service = payServiceMap.get(type);
if (service == null) {
throw new IllegalArgumentException("不支持的支付类型: " + type);
}
return service;
}
}这种写法的好处是:新增支付方式时新增一个 Bean 即可,不必修改工厂里的 if/else。
和策略模式区别
| 对比 | 工厂模式 | 策略模式 |
|---|---|---|
| 关注点 | 对象怎么创建或获取 | 行为算法怎么切换 |
| 解决问题 | 隔离创建逻辑 | 隔离业务变化逻辑 |
| 常见组合 | 工厂返回策略对象 | 策略执行业务 |
实际项目里二者经常一起用:工厂负责找到对应策略,策略负责执行具体逻辑。
滥用后果
| 滥用方式 | 后果 |
|---|---|
| 类型只有一个也抽工厂 | 增加无意义类和跳转成本 |
| 工厂里塞业务逻辑 | 工厂职责膨胀,难测试 |
| 过度抽象成多层工厂 | 新人难理解,维护成本高 |
| 没有统一异常 | 不支持类型时错误不清楚 |
面试标准回答
工厂模式用于封装对象创建和选择逻辑,让业务代码依赖接口而不是依赖具体实现。它的重点不是少写 new,而是把具体实现类、创建参数、初始化过程和选择规则集中起来。比如支付系统中订单服务只依赖 PayService,PayServiceFactory 根据渠道返回支付宝、微信或银联实现。这样新增渠道时主要新增实现类,业务主流程不需要跟着改。它的代价是增加一层跳转,所以只有实现会扩展、创建逻辑复杂或调用方不应该知道具体类时才适合使用。常见追问:
| 追问 | 回答要点 |
|---|---|
| 简单工厂违反开闭原则吗 | 新增类型要改工厂,所以严格说不完全符合,但类型少、变化小的场景可以接受 |
| Spring Bean Map 是工厂吗 | 是一种常见工程化工厂写法,利用 Spring 管理对象生命周期,工厂只负责按 key 选择 |
| 工厂和策略怎么配合 | 工厂负责找到策略对象,策略负责执行业务规则 |
| 工厂里能不能写业务 | 不建议。工厂只做对象获取和选择,业务流程放 Service 或具体实现 |
小结
工厂模式适合对象创建逻辑会变化、调用方不应该关心具体实现类的场景。它的关键不是“把 new 换个地方写”,而是把创建规则集中管理,让业务代码依赖抽象而不是依赖具体类。
