设计模式实战解读(二):工厂模式——对象创建的解耦艺术

本文是「设计模式实战解读」系列第二篇。系列文章统一按照 定义 → 痛点场景 → 模式结构 → 核心实现 → 真实应用 → 常见变种 → 优缺点 → 避坑指南 → FAQ 的结构展开,每篇聚焦一个模式讲透。


一句话定义

工厂模式(Factory):把对象的创建逻辑从使用者中剥离出来,由专门的工厂负责创建。调用方只关心"我要什么",不关心"怎么造"。

归属:创建型模式。


一、没有工厂时的痛点

假设你在做一个消息通知模块,需要支持多种发送渠道:

java 复制代码
public void sendNotification(String channel, String message) {
    if ("email".equals(channel)) {
        EmailSender sender = new EmailSender("smtp.xxx.com", 465, "user", "pwd");
        sender.send(message);
    } else if ("sms".equals(channel)) {
        SmsSender sender = new SmsSender("https://sms-api.xxx.com", "appKey", "secret");
        sender.send(message);
    } else if ("dingtalk".equals(channel)) {
        DingTalkSender sender = new DingTalkSender("webhook_url", "sign_secret");
        sender.send(message);
    } else if ("wechat".equals(channel)) {
        WeChatSender sender = new WeChatSender("corpId", "agentId", "secret");
        sender.send(message);
    }
}

问题:

  1. 违反开闭原则:每新增一种渠道,都要改这段代码
  2. 创建逻辑散落各处:多处需要创建 Sender,重复的构造参数到处写
  3. 调用方强耦合具体类:具体类改了,所有调用方都要改
  4. 无法做单元测试:直接 new 出来的对象没法被 mock

二、模式结构

工厂模式有三个层次:

复制代码
层次 1: 简单工厂
    Client → Factory.create(type) → ProductA / ProductB / ProductC

层次 2: 工厂方法
    Client → AbstractFactory.create() → Product
              ↑ FactoryA → ProductA
              ↑ FactoryB → ProductB

层次 3: 抽象工厂
    Client → AbstractFactory
              ├ createProductX() → X1 / X2
              └ createProductY() → Y1 / Y2

三个层次的适用边界:

  • 简单工厂:产品种类少(<10),新增频率低
  • 工厂方法:产品种类多,经常新增,要做到加新品不改旧代码
  • 抽象工厂:需要创建一组相关联的产品(如 UI 组件族:按钮+输入框+下拉框)

三、核心实现

3.1 简单工厂

java 复制代码
// 产品接口
public interface MessageSender {
    void send(String message, String target);
}

// 具体产品
public class EmailSender implements MessageSender { /* ... */ }
public class SmsSender implements MessageSender { /* ... */ }
public class DingTalkSender implements MessageSender { /* ... */ }

// 简单工厂
public class SenderFactory {
    private static final Map<String, Supplier<MessageSender>> CREATORS = new HashMap<>();

    static {
        CREATORS.put("email", EmailSender::new);
        CREATORS.put("sms", SmsSender::new);
        CREATORS.put("dingtalk", DingTalkSender::new);
    }

    public static MessageSender create(String channel) {
        Supplier<MessageSender> creator = CREATORS.get(channel);
        if (creator == null) {
            throw new IllegalArgumentException("Unsupported channel: " + channel);
        }
        return creator.get();
    }
}

// 调用方(解耦了!)
MessageSender sender = SenderFactory.create("email");
sender.send("Hello", "test@example.com");

Map<String, Supplier> 代替 if-else,新增渠道只需往 Map 里注册,不改工厂逻辑。

3.2 工厂方法

当产品创建逻辑差异大(需要不同配置、不同依赖)时,每种产品对应一个工厂:

java 复制代码
// 抽象工厂
public interface SenderFactory {
    MessageSender create(SenderConfig config);
}

// 具体工厂
public class EmailSenderFactory implements SenderFactory {
    @Override
    public MessageSender create(SenderConfig config) {
        // Email 特有的初始化:SMTP 连接、SSL 证书加载
        SmtpClient smtp = new SmtpClient(config.getHost(), config.getPort());
        smtp.auth(config.getUsername(), config.getPassword());
        return new EmailSender(smtp);
    }
}

public class DingTalkSenderFactory implements SenderFactory {
    @Override
    public MessageSender create(SenderConfig config) {
        // 钉钉特有的初始化:签名计算、Token 缓存
        DingTalkClient client = new DingTalkClient(config.getWebhook());
        client.setSignSecret(config.getSecret());
        return new DingTalkSender(client);
    }
}

调用方通过注入不同工厂来切换产品,完全不耦合具体产品类。

3.3 结合 Spring 的工厂注册

在 Spring 项目中,工厂模式最常见的落地方式是策略 + 自动注册

java 复制代码
// 每种渠道的 Sender 标注自己的类型
@Component("email")
public class EmailSender implements MessageSender { /* ... */ }

@Component("sms")
public class SmsSender implements MessageSender { /* ... */ }

@Component("dingtalk")
public class DingTalkSender implements MessageSender { /* ... */ }

// 工厂:利用 Spring 自动收集所有实现
@Component
public class SenderFactory {
    private final Map<String, MessageSender> senderMap;

    // Spring 会自动把所有 MessageSender 按 Bean Name 注入到 Map
    public SenderFactory(Map<String, MessageSender> senderMap) {
        this.senderMap = senderMap;
    }

    public MessageSender getSender(String channel) {
        MessageSender sender = senderMap.get(channel);
        if (sender == null) {
            throw new IllegalArgumentException("No sender for: " + channel);
        }
        return sender;
    }
}

这是实际项目中用得最多的模式------零手动注册,新增一个 @Component 就自动生效


四、真实应用场景

4.1 框架级应用

框架 工厂在哪 创建什么
Spring BeanFactory Bean 实例
MyBatis SqlSessionFactory SqlSession
SLF4J LoggerFactory Logger
JDBC DriverManager Connection
Jackson ObjectMapper (builder) Serializer/Deserializer
Netty ChannelFactory Channel

4.2 业务场景

业务 工厂 产品 为什么用工厂
消息通知 SenderFactory 邮件/短信/钉钉/飞书 渠道多、各自初始化逻辑不同
支付 PaymentFactory 微信/支付宝/银联 支付方式差异大,策略隔离
文件解析 ParserFactory Excel/CSV/JSON/XML 文件格式各自一套解析逻辑
连接器 ConnectorFactory HTTP/RPC/MQ/DB 协议不同,连接方式差异大
报告导出 ExporterFactory PDF/Word/HTML 渲染逻辑完全不同
规则引擎 ConditionFactory 等于/包含/大于/正则 条件类型多,各自求值逻辑独立

4.3 iPaaS 连接器场景

在 iPaaS 平台中,连接器(Connector)是工厂模式的经典落地:

复制代码
用户配置的流程节点 → 指定了 connectorType = "dingtalk"
                          ↓
              ConnectorFactory.create("dingtalk")
                          ↓
              DingTalkConnector(已初始化好 Token 缓存、签名算法)

连接器的创建涉及 OAuth 初始化、Token 刷新、签名算法加载、SDK 实例化等重逻辑,不可能让业务代码直接 new。工厂把这些复杂性封装起来,调用方只需 factory.create(type) 拿到一个可用的连接器实例。


五、常见变种

5.1 静态工厂方法

不新建工厂类,在产品类上加静态方法:

java 复制代码
public class Connection {
    private Connection(String url) { /* ... */ }
    
    // 静态工厂方法(命名比 new 更有语义)
    public static Connection of(String url) { return new Connection(url); }
    public static Connection pooled(String url, int poolSize) { /* ... */ }
    public static Connection readonly(String url) { /* ... */ }
}

JDK 中大量使用:List.of()Optional.of()Integer.valueOf()Collections.emptyList()

优势:方法名有语义 + 可以返回缓存实例 + 可以返回子类型。

5.2 配置驱动工厂

产品类型由配置文件/数据库决定,工厂在运行时动态加载:

java 复制代码
// 从配置中读取所有渠道 → 类名映射
// channel.email=com.xxx.EmailSender
// channel.sms=com.xxx.SmsSender
public class DynamicFactory {
    private final Map<String, Class<?>> registry = new HashMap<>();

    public void register(String type, String className) throws ClassNotFoundException {
        registry.put(type, Class.forName(className));
    }

    public MessageSender create(String type) throws Exception {
        return (MessageSender) registry.get(type).getDeclaredConstructor().newInstance();
    }
}

这种方式配合 SPI(ServiceLoader)可以做到"不改代码、加 jar 包就能新增产品"。

5.3 延迟创建工厂

有些产品创建成本高,工厂不立即创建,而是返回一个"工厂引用":

java 复制代码
public class LazyFactory {
    public Supplier<HeavyObject> createLazy(Config config) {
        // 不立即创建,返回一个 Supplier
        return () -> new HeavyObject(config);
    }
}

// 使用方在真正需要时才调用 .get()
Supplier<HeavyObject> lazy = factory.createLazy(config);
// ... 很多逻辑 ...
HeavyObject obj = lazy.get(); // 这时才真正创建

六、优缺点

优点 缺点
解耦创建逻辑和使用逻辑 增加类的数量(每种产品一个类/工厂)
符合开闭原则(新增产品不改工厂) 简单场景过度使用反而增加复杂度
集中管理创建逻辑,修改一处生效全局 产品需要遵循统一接口(有时会过度抽象)
方便做缓存、池化、日志统计 调用方丧失了对象初始化的细粒度控制
对单元测试友好(可以注入 mock 工厂) ---

七、避坑指南

坑 1:简单工厂退化成巨型 if-else

Map<String, Supplier> 或 Spring 自动注入代替 if-else。如果仍然用 if-else,每次加渠道都改工厂类,开闭原则形同虚设。

坑 2:工厂方法过度使用

如果产品只有 2-3 种且不太会增加,直接用简单工厂就够了。为 3 种产品建 3 个工厂类 + 1 个抽象工厂,属于过度工程化。

坑 3:工厂和产品的依赖注入冲突

在 Spring 项目中,如果产品本身已经被 Spring 管理(@Component),那用 Map<String, T> 自动注入比手写工厂更简洁。别自己 new 产品再放进 Map------让 Spring 帮你干。

坑 4:工厂创建的对象未做生命周期管理

工厂创建了对象,但没人负责关闭/销毁------连接泄漏、线程泄漏。工厂如果创建有资源的对象(连接、流),应该同时提供 destroy() 或配合连接池使用。

坑 5:产品接口过度抽象

为了"统一",把完全不同的东西强行抽象成一个接口。比如 EmailSender 和 WebhookSender 的参数完全不同(邮件有收件人/抄送/附件,Webhook 只有 URL),非要统一成 send(String msg) 会导致各种 hack。

解法:允许产品接口有泛型参数,或者把差异部分放到 Config 对象里。


八、常见问题(FAQ)

Q:简单工厂、工厂方法、抽象工厂,什么时候用哪个?

A:大部分业务场景,简单工厂就够了。只有当满足以下条件时才升级:产品种类多(>10)且频繁新增 → 用工厂方法;需要创建一组相关联的产品族 → 用抽象工厂。90% 的实际项目只需要简单工厂。

Q:工厂模式和策略模式有什么区别?

A:工厂模式关注的是"创建"------如何构造一个对象。策略模式关注的是"行为"------如何选择一种算法来执行。很多时候二者结合使用:工厂创建出策略对象,调用方使用策略对象。区别在于侧重点不同。

Q:Spring 的 BeanFactory 是不是工厂模式?

A:是的,而且是工厂模式最经典的工业级实现。它不仅创建 Bean,还管理 Bean 的生命周期(初始化、销毁)、作用域(singleton/prototype/request)、依赖注入、AOP 代理。可以理解为"超级工厂"。

Q:什么时候不应该用工厂?

A:对象创建逻辑简单(直接 new 就行、没有依赖)、产品不需要面向接口编程、不需要 mock 测试------这些场景直接 new 比引入工厂更简洁。不要为了设计模式而设计模式。

Q:工厂创建的对象应该是单例还是每次 new?

A:取决于产品是否有状态。无状态的服务类(如 Sender)可以缓存复用;有状态的实例(如正在使用的 Connection)每次创建。通常在工厂内加一层缓存判断即可。


九、小结

工厂模式的核心价值只有一句话:把 "new" 的权利从调用方收回来,交给专门的角色管理。

记住三个实践要点:

  1. Spring 项目优先用 Map<String, T> 自动注入,零代码实现工厂效果
  2. 简单工厂用 Map + Supplier 代替 if-else,满足开闭原则
  3. 工厂创建的对象别忘记生命周期管理------谁创建谁负责销毁

下一篇我们聊模板方法模式------当一系列操作有固定的"骨架"但细节各不相同时,如何做到复用骨架、开放细节。


标签:#设计模式 #工厂模式 #Factory #简单工厂 #工厂方法 #抽象工厂 #Java #Spring #创建型模式 #策略模式 #依赖注入 #面向对象 #软件工程

相关推荐
看山是山_Lau9 小时前
原型模式:当复制比重新创建更高效时
设计模式·原型模式
用户3563029048710 小时前
【设计模式】观察者模式——事件通知机制
设计模式
追烽少年x10 小时前
STL中的设计模式(二)
c++·设计模式
悟051510 小时前
设计模式-模板模式
设计模式
BLSxiaopanlaile11 小时前
有关创建型的几个设计模式总结
设计模式
蜡笔小马11 小时前
14.C++设计模式-状态模式
c++·设计模式·状态模式
加油201912 小时前
嵌入式软件技术栈和学习路线详解
linux·arm开发·数据结构·mqtt·设计模式·嵌入式
likerhood12 小时前
设计模式 · 代理模式(Proxy Pattern)java
java·设计模式·代理模式
XiYang-DING1 天前
【Spring】SpringIoC&DI
java·spring·log4j