5、(创建型设计模式)Java 工厂方法模式深度学习指南

以下是为你精心撰写的 《Java 工厂方法模式深度学习指南》,专为 Java 后端开发者设计,内容涵盖:定义、作用、真实业务场景、实现方式对比、Spring 中的体现、避坑指南、最佳实践、面试高频题,所有示例均含中文注释,可直接用于项目开发、重构与面试准备。


📘 Java 工厂方法模式深度学习指南

—— 从“new”到“工厂”的架构进化之路

作者:Java 后端架构实战导师
适用对象:Java 后端开发者(Spring Boot / 微服务 / 分布式系统)
目标:彻底理解工厂方法模式,摆脱“硬编码 new”,实现开闭原则解耦设计
核心原则“创建对象的责任,交给子类”


✅ 一、什么是工厂方法模式?

📌 定义:

工厂方法模式(Factory Method Pattern) 是一种创建型设计模式,它定义了一个创建对象的接口,但让子类决定实例化哪一个类。工厂方法让一个类的实例化延迟到其子类中进行。

🔍 核心思想:

  • 定义一个创建对象的抽象方法(工厂方法)
  • 由子类实现该方法,决定具体创建哪个类
  • 客户端只依赖抽象接口,不依赖具体实现

💡 一句话记忆
“我要一个产品,但不关心你怎么造,你(子类)来决定造什么。”

🆚 对比简单工厂 vs 工厂方法:

模式简单工厂工厂方法
实现方式一个静态方法,用 if-else 判断抽象类定义工厂方法,子类重写
是否符合开闭原则❌ 新增产品需修改工厂代码✅ 新增产品只需新增子类,不改旧代码
扩展性优秀
耦合度高(工厂类知道所有产品)低(客户端只依赖抽象)
推荐度仅用于简单场景✅ 推荐用于中大型系统

结论工厂方法模式是“简单工厂”的升级版,是真正的面向对象设计!


✅ 二、工厂方法模式有什么作用?(Why Use Factory Method?)

作用说明
解耦对象创建与使用客户端无需知道具体类名,只调用接口
支持开闭原则(OCP)新增产品类型时,无需修改现有代码,只需新增子类
提高可扩展性易于支持多种产品变体(如不同支付方式、不同日志格式)
统一接口,规范行为所有产品实现同一接口,客户端行为一致
便于测试可轻松用 Mock 对象替换真实实现
支持依赖注入(DI)与 Spring 容器完美融合

💡 经典比喻
你去餐厅点“汉堡”——你只说“我要一个汉堡”,
不关心是麦当劳的、肯德基的,还是本地小店的。
餐厅(工厂) 负责根据“菜单”决定做哪个。
→ 这就是工厂方法!


✅ 三、工厂方法模式的典型使用场景(Java 后端真实案例)

场景说明是否推荐使用工厂方法
支付方式支付宝、微信、银联、Apple Pay✅ 强烈推荐
日志输出控制台、文件、数据库、ELK✅ 推荐
消息发送短信、邮件、站内信、企业微信✅ 推荐
数据库驱动MySQL、PostgreSQL、Oracle✅ 推荐
序列化方式JSON、XML、Protobuf✅ 推荐
缓存实现Redis、Memcached、本地缓存✅ 推荐
文件读取器CSV、Excel、JSON 文件解析器✅ 推荐
工具类(如 StringUtils无状态,静态方法即可❌ 不推荐
简单对象(如 UserOrder通常直接 new,无必要封装❌ 不推荐

判断标准
“是否需要根据配置/环境/参数动态创建不同实现?”
→ 是 → 用工厂方法!


✅ 四、工厂方法模式的四种实现方式详解(含中文注释)

我们从基础结构Spring 集成,逐步深入。


🔹 1. 基础结构:抽象产品 + 抽象工厂 + 具体产品 + 具体工厂

📌 步骤:
  1. 定义产品接口(抽象产品)
  2. 定义工厂接口(抽象工厂)
  3. 实现具体产品类
  4. 实现具体工厂类

✅ 示例:支付系统(支付宝、微信、银联)
/**
 * 【1】抽象产品:支付接口(所有支付方式的统一契约)
 */
interface Payment {
    boolean pay(double amount); // 支付方法
}

/**
 * 【2】具体产品1:支付宝支付实现
 */
class Alipay implements Payment {
    @Override
    public boolean pay(double amount) {
        System.out.println("💳 支付宝支付成功,金额:" + amount + "元");
        return true;
    }
}

/**
 * 【3】具体产品2:微信支付实现
 */
class WeChatPay implements Payment {
    @Override
    public boolean pay(double amount) {
        System.out.println("📱 微信支付成功,金额:" + amount + "元");
        return true;
    }
}

/**
 * 【4】抽象工厂:支付工厂接口(定义创建产品的抽象方法)
 */
interface PaymentFactory {
    Payment createPayment(); // 工厂方法:由子类决定创建哪个支付方式
}

/**
 * 【5】具体工厂1:支付宝工厂
 */
class AlipayFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        return new Alipay(); // 创建支付宝对象
    }
}

/**
 * 【6】具体工厂2:微信工厂
 */
class WeChatPayFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        return new WeChatPay(); // 创建微信支付对象
    }
}

/**
 * 【7】客户端:调用者(只依赖抽象,不关心具体实现)
 */
public class PaymentClient {
    public static void main(String[] args) {
        // 模拟从配置文件读取支付方式
        String paymentType = "alipay";

        PaymentFactory factory;
        if ("alipay".equals(paymentType)) {
            factory = new AlipayFactory(); // 创建具体工厂
        } else if ("wechat".equals(paymentType)) {
            factory = new WeChatPayFactory();
        } else {
            throw new IllegalArgumentException("不支持的支付方式");
        }

        // ✅ 客户端只调用抽象工厂和抽象产品
        Payment payment = factory.createPayment(); // 调用工厂方法
        payment.pay(199.9); // 执行支付
        // 输出:💳 支付宝支付成功,金额:199.9元
    }
}
✅ 优点:
  • 完全符合开闭原则:新增“银联支付”只需新增 UnionPayUnionPayFactory无需修改任何已有代码
  • 客户端解耦:客户端不知道具体类名,只依赖接口
  • 易于测试:可 Mock PaymentFactory 返回 MockPayment
💡 为什么叫“方法”?

因为“创建对象”是通过一个方法createPayment())完成的,而不是通过 new 关键字。


🔹 2. 工厂方法 + 工厂注册表(Map + 反射)—— 更灵活的实现

避免在客户端写 if-else 判断,把选择权交给配置

import java.util.HashMap;
import java.util.Map;

/**
 * 【1】支付接口(同上)
 */
interface Payment {
    boolean pay(double amount);
}

class Alipay implements Payment {
    @Override
    public boolean pay(double amount) {
        System.out.println("💳 支付宝支付成功,金额:" + amount + "元");
        return true;
    }
}

class WeChatPay implements Payment {
    @Override
    public boolean pay(double amount) {
        System.out.println("📱 微信支付成功,金额:" + amount + "元");
        return true;
    }
}

/**
 * 【2】工厂注册表:使用 Map 存储工厂类名(避免硬编码)
 */
class PaymentFactoryRegistry {
    // 注册表:支付类型 -> 工厂类名
    private static final Map<String, Class<? extends PaymentFactory>> factoryMap = new HashMap<>();

    // 静态块:初始化注册表(可从配置文件读取)
    static {
        factoryMap.put("alipay", AlipayFactory.class);
        factoryMap.put("wechat", WeChatPayFactory.class);
    }

    /**
     * 根据支付类型获取工厂实例
     */
    public static PaymentFactory getFactory(String type) {
        try {
            Class<? extends PaymentFactory> factoryClass = factoryMap.get(type);
            if (factoryClass == null) {
                throw new IllegalArgumentException("未知支付类型:" + type);
            }
            return factoryClass.getDeclaredConstructor().newInstance(); // 反射创建
        } catch (Exception e) {
            throw new RuntimeException("创建工厂失败", e);
        }
    }
}

/**
 * 【3】抽象工厂接口(同上)
 */
interface PaymentFactory {
    Payment createPayment();
}

/**
 * 【4】具体工厂类(同上)
 */
class AlipayFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        return new Alipay();
    }
}

class WeChatPayFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        return new WeChatPay();
    }
}

/**
 * 【5】客户端:完全解耦,仅依赖配置
 */
public class RegistryFactoryDemo {
    public static void main(String[] args) {
        // 从配置文件或环境变量读取
        String paymentType = "alipay"; // 可从 application.yml 读取

        // ✅ 无需 if-else,通过注册表自动获取工厂
        PaymentFactory factory = PaymentFactoryRegistry.getFactory(paymentType);
        Payment payment = factory.createPayment();
        payment.pay(299.9);
        // 输出:💳 支付宝支付成功,金额:299.9元
    }
}
✅ 优点:
  • 新增支付方式:只需在 factoryMap 添加一行,无需修改客户端代码
  • 支持热加载:可从数据库或配置中心动态加载工厂类名
  • 真正符合开闭原则
⚠️ 注意:
  • 使用反射有性能开销,可缓存工厂实例
  • 非必要不推荐,适合大型可插拔系统

🔹 3. 工厂方法 + Spring 容器(最推荐的生产级写法)

在 Spring Boot 中,工厂方法模式是默认模式

/**
 * 【1】支付接口(抽象产品)
 */
public interface Payment {
    boolean pay(double amount);
}

/**
 * 【2】具体产品1:支付宝支付
 */
@Component("alipay") // ✅ Spring 管理的 Bean,名称为 "alipay"
public class Alipay implements Payment {
    @Override
    public boolean pay(double amount) {
        System.out.println("💳 支付宝支付成功,金额:" + amount + "元");
        return true;
    }
}

/**
 * 【3】具体产品2:微信支付
 */
@Component("wechat") // ✅ Spring 管理的 Bean,名称为 "wechat"
public class WeChatPay implements Payment {
    @Override
    public boolean pay(double amount) {
        System.out.println("📱 微信支付成功,金额:" + amount + "元");
        return true;
    }
}

/**
 * 【4】工厂类:Spring 管理的工厂(使用 @Bean + @Qualifier)
 * 这是工厂方法模式在 Spring 中的优雅体现!
 */
@Configuration
public class PaymentFactoryConfig {

    // ✅ 工厂方法:根据参数返回不同 Bean
    @Bean
    public Payment createPayment(@Value("${payment.type:alipay}") String paymentType) {
        // Spring 自动注入所有 Payment 类型的 Bean
        // 通过 @Qualifier 按名称获取
        return switch (paymentType.toLowerCase()) {
            case "alipay" -> applicationContext.getBean("alipay", Payment.class);
            case "wechat" -> applicationContext.getBean("wechat", Payment.class);
            default -> throw new IllegalArgumentException("不支持的支付方式:" + paymentType);
        };
    }

    @Autowired
    private ApplicationContext applicationContext; // 注入 Spring 上下文
}

/**
 * 【5】服务层:使用工厂
 */
@Service
public class OrderService {

    @Autowired
    private Payment payment; // ✅ Spring 自动注入正确实现(由工厂方法决定)

    public void processOrder(double amount) {
        System.out.println("🛒 开始处理订单,金额:" + amount);
        boolean result = payment.pay(amount); // 调用支付
        if (result) {
            System.out.println("✅ 订单支付成功");
        }
    }
}

/**
 * 【6】配置文件:application.yml
 */
/*
payment:
  type: wechat  # 可改为 alipay,无需改代码!
*/

/**
 * 【7】启动类测试
 */
@SpringBootApplication
public class SpringFactoryDemo {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringFactoryDemo.class, args);
        OrderService orderService = context.getBean(OrderService.class);
        orderService.processOrder(188.8);
        // 输出:
        // 🛒 开始处理订单,金额:188.8
        // 📱 微信支付成功,金额:188.8元
        // ✅ 订单支付成功
    }
}
✅ 优点:
  • 完全解耦:客户端(OrderService)不知道具体实现
  • 配置驱动:通过 application.yml 动态切换支付方式
  • 无需手写工厂类:Spring 自动管理 Bean 生命周期
  • 支持 AOP、事务、缓存:所有 Spring 功能自动生效
💡 这是真正的“工厂方法模式”在企业级开发中的体现!

你每天都在用

  • @Autowired JdbcTemplate
  • @Autowired MessageConverter
  • @Autowired TaskExecutor
    → 都是 Spring 工厂方法模式的体现!

🔹 4. 工厂方法 + 枚举 + 策略模式(企业级最佳实践)

将工厂方法与枚举结合,实现类型安全、零反射、易扩展

/**
 * 【1】支付接口
 */
interface Payment {
    boolean pay(double amount);
}

/**
 * 【2】具体实现
 */
class Alipay implements Payment {
    @Override
    public boolean pay(double amount) {
        System.out.println("💳 支付宝支付成功,金额:" + amount + "元");
        return true;
    }
}

class WeChatPay implements Payment {
    @Override
    public boolean pay(double amount) {
        System.out.println("📱 微信支付成功,金额:" + amount + "元");
        return true;
    }
}

/**
 * 【3】支付方式枚举(核心:工厂方法嵌入枚举)
 * 每个枚举常量是一个“工厂”,返回对应支付实例
 */
public enum PaymentType {
    ALIPAY {
        @Override
        public Payment create() {
            return new Alipay();
        }
    },
    WECHAT {
        @Override
        public Payment create() {
            return new WeChatPay();
        }
    };

    // 抽象工厂方法:每个枚举实现自己的创建逻辑
    public abstract Payment create();

    // 静态方法:根据字符串获取枚举
    public static PaymentType fromString(String type) {
        for (PaymentType pt : values()) {
            if (pt.name().equalsIgnoreCase(type)) {
                return pt;
            }
        }
        throw new IllegalArgumentException("未知支付类型:" + type);
    }
}

/**
 * 【4】客户端:极简调用
 */
public class EnumFactoryDemo {
    public static void main(String[] args) {
        String paymentType = "alipay";
        Payment payment = PaymentType.fromString(paymentType).create(); // ✅ 一行创建
        payment.pay(99.9);
        // 输出:💳 支付宝支付成功,金额:99.9元
    }
}
✅ 优点:
  • 类型安全:编译期检查,避免拼写错误
  • 无反射:性能高
  • 防非法值:枚举值有限,不可伪造
  • 可序列化:枚举天然支持
  • 代码极简
💡 推荐场景

支付、协议、状态、权限类型有限且稳定的枚举场景


✅ 五、工厂方法模式的避坑指南(Java 后端高频踩坑)

问题原因解决方案
❌ 用 new 直接创建对象耦合严重,无法扩展✅ 改为通过工厂方法获取
❌ 工厂类中用 if-else 判断类型违反开闭原则✅ 用 Map + 配置 或 枚举
❌ 工厂类依赖具体实现类导致“工厂膨胀”✅ 所有产品实现同一接口
❌ 忘记使用抽象接口客户端依赖具体类✅ 所有变量声明为接口类型:Payment payment = factory.create();
❌ 在 Spring 中手写工厂而不用 @Bean重复造轮子✅ 优先用 Spring 容器管理
❌ 多线程下工厂创建对象不安全若对象有状态✅ 确保产品是无状态或线程安全的

✅ 六、工厂方法模式 vs 其他模式对比

模式区别适用场景
工厂方法抽象工厂定义方法,子类决定实现一个产品族,多个实现(如支付)
抽象工厂创建多个产品族(如 UI 主题 + 按钮+菜单)多套完整产品组合(如 Windows/Mac 主题)
简单工厂一个类包含所有创建逻辑小型项目,无扩展需求
建造者分步构建复杂对象构建有多个参数的对象(如 SQL、DTO)
策略模式算法可互换同一接口不同行为(如排序、折扣)

记住
工厂方法 = “我需要一个产品,你来决定造哪个”
策略模式 = “我需要一个算法,你来决定用哪个”


✅ 七、学习建议与进阶路径

阶段建议
📚 第一周在你的项目中,把所有 new Alipay() 替换为 PaymentFactory.getPayment("alipay")
📚 第二周用 Spring 的 @Component + @Value 实现支付方式动态切换
📚 第三周用枚举工厂重构“日志输出方式”(控制台/文件/数据库)
📚 第四周阅读 Spring 源码:BeanFactory.getBean() 如何实现工厂方法?
📚 面试准备准备回答:

“你项目中哪里用了工厂方法?”
“Spring 中的 @Bean 是工厂方法吗?”
“工厂方法和策略模式区别?”
“为什么不用简单工厂?” |


✅ 八、总结:工厂方法模式选型决策树

graph TD
    A[需要创建不同对象吗?] --> B{对象类型是否有限且稳定?}
    B -->|是| C[使用枚举工厂(PaymentType.ALIPAY.create())]
    B -->|否| D{是否在 Spring 项目中?}
    D -->|是| E[使用 @Component + @Value + @Autowired,让 Spring 管理]
    D -->|否| F[使用抽象工厂接口 + 具体工厂类]
    G[是否需要热插拔?] --> H[使用 Map + 配置文件 + 反射]

最终推荐

  • Spring 项目 → ✅ @Component + @Value(最优雅)
  • 非 Spring、类型固定 → ✅ 枚举工厂(最安全)
  • 非 Spring、需扩展 → ✅ 抽象工厂 + 具体工厂(最标准)
  • 绝对不要:简单工厂(if-else)、手写 new

✅ 九、工厂方法模式面试高频题(附答案)

Q1:什么是工厂方法模式?和简单工厂有什么区别?

A:工厂方法定义一个创建对象的接口,由子类决定实例化哪个类;简单工厂是一个类用 if-else 统一创建,违反开闭原则。

Q2:Spring 中的 @Bean 是工厂方法吗?

A:是的!@Bean 方法就是工厂方法,Spring 容器调用它来创建 Bean。

Q3:工厂方法模式如何实现开闭原则?

A:新增产品时,只需新增具体产品类和具体工厂类,无需修改客户端或抽象工厂代码。

Q4:工厂方法模式适合什么场景?

A:需要根据配置/环境创建不同实现的场景,如支付、日志、序列化、数据库驱动。

Q5:工厂方法模式和策略模式的区别?

A:

  • 工厂方法:创建对象(谁来造?)
  • 策略模式:执行行为(怎么干?)
    举例:工厂创建“支付宝”对象,策略决定“用支付宝支付”还是“用微信支付”。

✅ 十、结语:真正的高手,不写 new,而写 @Bean

你不是在学“工厂方法”,你是在学“如何让系统不因变化而崩溃”。

当你看到 @Autowired Payment payment 时,你已经站在了工厂方法的巅峰。
当你用 application.yml 切换支付方式而不改一行代码时,你已经掌握了开闭原则。

不要为了“用模式”而用模式,
要为了“系统可扩展、可维护、可配置”而选择它。


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值