Skip to content

工厂模式

工厂模式用于封装对象创建逻辑。调用方不直接 new 具体类,而是通过工厂获取对象,从而降低调用方和具体实现类之间的耦合。

为什么需要工厂模式

如果业务代码到处写 new AliPayService()new WechatPayService(),当创建逻辑变化时,所有调用点都要改。

mermaid
flowchart TD
    A["业务代码"] --> B{"支付类型"}
    B -- "ali" --> C["new AliPayService"]
    B -- "wechat" --> D["new WechatPayService"]
    B -- "union" --> E["new UnionPayService"]

问题:

  1. 业务代码知道太多具体类。
  2. 新增类型时要修改很多 if/else。
  3. 创建对象前的参数校验、配置读取、客户端初始化会散落各处。

工厂模式把“创建什么对象”集中管理:

mermaid
flowchart TD
    A["业务代码"] --> B["PayFactory"]
    B --> C{"根据 type 选择"}
    C -- "ali" --> D["AliPayService"]
    C -- "wechat" --> E["WechatPayService"]
    C -- "union" --> F["UnionPayService"]

简单工厂

适合类型少、变化不频繁的场景。

java
public interface PayService {
    void pay(BigDecimal amount);
}
java
public class AliPayService implements PayService {
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("ali pay: " + amount);
    }
}
java
public class WechatPayService implements PayService {
    @Override
    public void pay(BigDecimal amount) {
        System.out.println("wechat pay: " + amount);
    }
}
java
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);
    }
}

调用:

java
PayService payService = PayFactory.create("ali");
payService.pay(new BigDecimal("100.00"));

简单工厂的问题是:新增支付方式时还是要改工厂类。类型很少时可接受;类型经常扩展时,要考虑工厂方法或 Spring Bean Map。

工厂方法

工厂方法把每种产品的创建交给独立工厂。

mermaid
flowchart TD
    A["PayFactory 接口"] --> B["AliPayFactory"]
    A --> C["WechatPayFactory"]
    B --> D["AliPayService"]
    C --> E["WechatPayService"]
java
public interface PayFactory {
    PayService create();
}
java
public class AliPayFactory implements PayFactory {
    @Override
    public PayService create() {
        return new AliPayService();
    }
}

工厂方法适合创建过程复杂、每种产品创建逻辑差异大的场景。但如果只是简单 new,会显得类太多。

Spring 中的工厂写法

Spring 项目中常见写法是让每个实现类成为 Bean,再通过 Map 选择。

商业场景:多支付渠道路由

电商、医疗缴费、SaaS 订阅系统里,支付渠道通常不会只有一种。真实项目里还会出现这些变化:

变化点例子如果写死在业务代码里会怎样
渠道新增新增支付宝、微信、银联、医保支付下单、退款、对账、回调多个地方都要改
渠道配置不同AppId、密钥、回调地址、超时时间不同配置读取散落,容易拿错密钥
渠道能力不同有的支持退款,有的支持分账,有的支持医保结算if/else 会扩散到业务主流程
渠道降级某渠道故障时切换备用渠道业务代码和故障处理耦合

比较合理的拆法是:业务层只表达“我要支付”,工厂负责根据订单、租户、渠道类型找到具体支付实现。

mermaid
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 工厂

支付请求:

java
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;
    }
}

统一接口:

java
public interface PayService {
    String channel();

    PayResult pay(PayRequest request);
}

支付宝实现:

java
@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");
    }
}

微信实现:

java
@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");
    }
}

工厂:

java
@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;
    }
}

业务层:

java
@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,而是对象获取背后的变化:

  1. 类名变化:调用方不关心具体实现类叫什么。
  2. 创建参数变化:实现类需要密钥、配置、客户端、连接池时,不污染业务层。
  3. 实现数量变化:新增实现时尽量只新增类,不修改主流程。
  4. 生命周期变化:在 Spring 中对象由容器管理,调用方不自己管理单例、依赖和销毁。
  5. 选择规则变化:可以从配置、租户、订单类型、灰度规则里选择实现。

不用工厂时,业务代码容易从“做业务”变成“判断用哪个类、怎么创建类、怎么初始化类”。这会让业务层同时承担流程编排和对象创建两种职责。

mermaid
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

核心原理

工厂模式的原理是把“选择具体实现类”的流程从业务代码中抽离出来,让业务代码只依赖接口。

mermaid
flowchart TD
    A["业务输入 type"] --> B["工厂读取注册表或判断规则"]
    B --> C["找到具体实现类"]
    C --> D["返回接口类型对象"]
    D --> E["业务代码调用接口方法"]

这样做的关键不是少写一个 new,而是把变化点集中到工厂或注册表里。调用方只知道 PayService,不需要知道 AliPayServiceWechatPayService 的创建细节。

java
public interface PayService {
    String type();

    void pay(BigDecimal amount);
}
java
@Component
public class AliPayService implements PayService {
    @Override
    public String type() {
        return "ali";
    }

    @Override
    public void pay(BigDecimal amount) {
        System.out.println("ali pay: " + amount);
    }
}
java
@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。

和策略模式区别

对比工厂模式策略模式
关注点对象怎么创建或获取行为算法怎么切换
解决问题隔离创建逻辑隔离业务变化逻辑
常见组合工厂返回策略对象策略执行业务

实际项目里二者经常一起用:工厂负责找到对应策略,策略负责执行具体逻辑。

滥用后果

滥用方式后果
类型只有一个也抽工厂增加无意义类和跳转成本
工厂里塞业务逻辑工厂职责膨胀,难测试
过度抽象成多层工厂新人难理解,维护成本高
没有统一异常不支持类型时错误不清楚

面试标准回答

text
工厂模式用于封装对象创建和选择逻辑,让业务代码依赖接口而不是依赖具体实现。它的重点不是少写 new,而是把具体实现类、创建参数、初始化过程和选择规则集中起来。比如支付系统中订单服务只依赖 PayService,PayServiceFactory 根据渠道返回支付宝、微信或银联实现。这样新增渠道时主要新增实现类,业务主流程不需要跟着改。它的代价是增加一层跳转,所以只有实现会扩展、创建逻辑复杂或调用方不应该知道具体类时才适合使用。

常见追问:

追问回答要点
简单工厂违反开闭原则吗新增类型要改工厂,所以严格说不完全符合,但类型少、变化小的场景可以接受
Spring Bean Map 是工厂吗是一种常见工程化工厂写法,利用 Spring 管理对象生命周期,工厂只负责按 key 选择
工厂和策略怎么配合工厂负责找到策略对象,策略负责执行业务规则
工厂里能不能写业务不建议。工厂只做对象获取和选择,业务流程放 Service 或具体实现

小结

工厂模式适合对象创建逻辑会变化、调用方不应该关心具体实现类的场景。它的关键不是“把 new 换个地方写”,而是把创建规则集中管理,让业务代码依赖抽象而不是依赖具体类。