3、Java 工厂方法模式从入门到实战

Java 工厂方法模式从入门到实战(后端必看)

前言:工厂方法模式是Java创建型设计模式的基础,也是开发者接触最多、最易上手的设计模式之一。它解决了简单工厂模式"职责过重、违反开闭原则"的核心痛点,在日常开发(如接口适配、组件实例化)和框架底层(Spring、MyBatis)中应用广泛。很多开发者分不清它与简单工厂、抽象工厂的区别,面试时只能说清表面用法,无法结合实战场景阐述。本文从入门到实战,结合真实业务场景+可运行代码+面试高频考点,带你吃透工厂方法模式,看完直接能用、能说、能面试,新手也能快速上手。

一、为什么需要工厂方法模式?(痛点直击)

在学习工厂方法模式前,先想一个Java开发中最常见的场景:假设你开发一个电商系统的支付模块,初期只支持微信支付,后来需要新增支付宝支付、银联支付,你会怎么写代码?

用简单工厂模式的弊端的很明显:

  • 所有支付方式的创建逻辑都写在一个工厂类中,随着支付方式增多,工厂类代码会越来越臃肿,职责过重,难以维护;

  • 新增支付方式(如银联支付)时,需要修改工厂类的判断逻辑,违反"开闭原则"(对扩展开放、对修改关闭);

  • 代码耦合度高,客户端需要知道具体支付类的名称,一旦类名修改,客户端代码也需要同步修改。

而工厂方法模式的核心价值,就是解决上述问题------将具体产品的创建逻辑拆分到对应工厂,让每个工厂只负责一个产品的创建,新增产品时只需新增工厂,无需修改原有代码

核心结论:当系统中存在"单一类型、多具体实现"的产品(如多种支付方式、多种日志实现),且需要灵活扩展、降低耦合时,就用工厂方法模式。

二、工厂方法模式核心概念(极简理解)

工厂方法模式的核心思想:"定义一个创建产品的抽象工厂接口,每个具体产品对应一个具体工厂,具体工厂实现抽象工厂的方法,负责创建对应的具体产品"

简单说,工厂方法模式是"一个工厂造一个产品",区别于抽象工厂的"一个工厂造一个系列产品"、简单工厂的"一个工厂造所有产品"。

工厂方法模式有4个核心角色(理论结合实战,重点记实战用法,避免死记硬背):

  1. Product(抽象产品):定义所有具体产品的统一接口,规范产品的核心行为(如支付接口、日志接口),所有具体产品都必须实现这个接口;

  2. ConcreteProduct(具体产品):实现抽象产品接口,是工厂方法模式最终创建的实例(如微信支付、支付宝支付、Log4j日志);

  3. Factory(抽象工厂):核心接口,定义一个创建抽象产品的方法(如创建支付的方法),具体工厂必须实现这个接口;

  4. ConcreteFactory(具体工厂):实现抽象工厂接口,重写创建产品的方法,负责创建某一个具体产品(如微信支付工厂只创建微信支付实例)。

重点区分:抽象产品和具体产品是"接口-实现"的关系,抽象工厂和具体工厂也是"接口-实现"的关系;每个具体工厂与具体产品一一对应,确保职责单一。

三、实战一:手写工厂方法模式(基础必备)

以电商系统"支付模块"为场景,手动实现工厂方法模式,理解其底层逻辑。场景说明:支持微信支付、支付宝支付两种方式,后续可扩展银联支付,要求新增支付方式时不修改原有代码。

步骤1:定义抽象产品(统一产品接口)

定义支付接口,规范所有支付方式的核心行为(支付、查询支付状态),所有具体支付类都要实现这个接口。

java 复制代码
/**
 * 抽象产品:支付接口(规范所有支付方式的统一行为)
 */
public interface Payment {
    // 核心支付方法
    void pay(double amount);
    // 查询支付状态(订单号)
    String queryPayStatus(String orderNo);
}

步骤2:定义具体产品(实现抽象产品接口)

实现微信支付、支付宝支付两个具体产品,各自实现支付接口的方法,封装自身的业务逻辑。

java 复制代码
/**
 * 具体产品1:微信支付(实现支付接口)
 */
public class WeChatPayment implements Payment {
    @Override
    public void pay(double amount) {
        // 模拟微信支付的业务逻辑(实际中调用微信支付SDK)
        System.out.println("微信支付:已完成扣款" + amount + "元");
    }

    @Override
    public String queryPayStatus(String orderNo) {
        // 模拟查询支付状态
        return "微信支付订单[" + orderNo + "]:支付成功";
    }
}

/**
 * 具体产品2:支付宝支付(实现支付接口)
 */
public class AlipayPayment implements Payment {
    @Override
    public void pay(double amount) {
        // 模拟支付宝支付的业务逻辑(实际中调用支付宝SDK)
        System.out.println("支付宝支付:已完成扣款" + amount + "元");
    }

    @Override
    public String queryPayStatus(String orderNo) {
        // 模拟查询支付状态
        return "支付宝支付订单[" + orderNo + "]:支付成功";
    }
}

步骤3:定义抽象工厂(统一工厂接口)

定义支付工厂接口,只包含一个创建支付产品的方法,所有具体支付工厂都要实现这个接口。

java 复制代码
/**
 * 抽象工厂:支付工厂接口(定义创建支付产品的方法)
 */
public interface PaymentFactory {
    // 核心方法:创建支付产品
    Payment createPayment();
}

步骤4:定义具体工厂(实现产品创建逻辑)

每个具体产品对应一个具体工厂,微信支付对应微信支付工厂,支付宝支付对应支付宝支付工厂,各自实现创建对应产品的逻辑。

java 复制代码
/**
 * 具体工厂1:微信支付工厂(只负责创建微信支付实例)
 */
public class WeChatPaymentFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        // 封装微信支付的创建细节(如初始化SDK、配置参数)
        return new WeChatPayment();
    }
}

/**
 * 具体工厂2:支付宝支付工厂(只负责创建支付宝支付实例)
 */
public class AlipayPaymentFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        // 封装支付宝支付的创建细节
        return new AlipayPayment();
    }
}

步骤5:客户端使用(调用工厂获取产品)

客户端无需关心具体产品的创建细节,只需获取对应的具体工厂,调用工厂的创建方法即可获取产品,实现"按需获取产品"。

java 复制代码
/**
 * 客户端:电商支付模块调用示例
 * 核心:通过具体工厂获取产品,无需关心产品创建细节
 */
public class PaymentClient {
    public static void main(String[] args) {
        // 1. 使用微信支付(获取微信工厂,创建微信支付产品)
        PaymentFactory weChatFactory = new WeChatPaymentFactory();
        Payment weChatPay = weChatFactory.createPayment();
        weChatPay.pay(199.9);
        System.out.println(weChatPay.queryPayStatus("WECHAT20260415001"));

        System.out.println("----------------------");

        // 2. 使用支付宝支付(获取支付宝工厂,创建支付宝支付产品)
        PaymentFactory alipayFactory = new AlipayPaymentFactory();
        Payment alipay = alipayFactory.createPayment();
        alipay.pay(299.9);
        System.out.println(alipay.queryPayStatus("ALIPAY20260415001"));
    }
}

运行结果(重点观察)

text 复制代码
微信支付:已完成扣款199.9元
微信支付订单[WECHAT20260415001]:支付成功
----------------------
支付宝支付:已完成扣款299.9元
支付宝支付订单[ALIPAY20260415001]:支付成功

手写工厂方法核心要点(避坑)

  • 抽象工厂只定义创建产品的方法,不负责具体创建逻辑,具体逻辑由具体工厂实现,符合"单一职责原则";

  • 具体工厂与具体产品一一对应,一个工厂只创建一个产品,避免工厂职责过重;

  • 客户端只依赖抽象工厂和抽象产品,不依赖具体工厂和具体产品,降低耦合度;

  • 新增产品(如银联支付)时,只需新增"银联支付产品类"和"银联支付工厂类",无需修改原有代码,完全符合"开闭原则"。

四、实战二:工厂方法模式进阶(结合Spring,实战常用)

实际企业开发中,我们不会手动new具体工厂,而是结合Spring框架的依赖注入(DI),将具体工厂交给Spring容器管理,客户端通过Spring获取工厂实例,进一步降低耦合,这也是主流开发方式。

注意:Spring配置中避免使用失效链接,本次采用更稳定的注解配置为主,XML配置为辅(适配旧项目)。

步骤1:将具体工厂交给Spring管理(两种方式)

方式一:注解方式(推荐,简洁高效,无需XML配置)

java 复制代码
import org.springframework.stereotype.Component;

// 微信支付工厂:交给Spring管理,指定bean名称(方便客户端获取)
@Component("weChatPaymentFactory")
public class WeChatPaymentFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        // 实际开发中,可通过@Autowired注入具体产品,无需手动new
        return new WeChatPayment();
    }
}

// 支付宝支付工厂:交给Spring管理
@Component("alipayPaymentFactory")
public class AlipayPaymentFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        return new AlipayPayment();
    }
}

方式二:XML配置方式(兼容旧项目,避免失效链接)

xml 复制代码
<!-- Spring配置文件:spring-beans.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 注册微信支付工厂bean -->
    <bean id="weChatPaymentFactory" class="com.example.factory.WeChatPaymentFactory"/>
    <!-- 注册支付宝支付工厂bean -->
    <bean id="alipayPaymentFactory" class="com.example.factory.AlipayPaymentFactory"/>

</beans>

说明:若XML配置中出现链接失效(link dead),可替换为Spring对应版本的本地XSD文件,或升级Spring依赖版本,确保配置文件正常加载。

步骤2:客户端通过Spring获取工厂和产品

通过Spring的ApplicationContext获取具体工厂,再调用工厂方法创建产品,无需手动new对象,彻底解耦。

java 复制代码
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 结合Spring的客户端调用(企业开发实战版)
 */
public class SpringPaymentClient {
    public static void main(String[] args) {
        // 1. 加载Spring配置,获取容器(XML配置方式)
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");

        // 2. 获取微信支付工厂(通过bean名称)
        PaymentFactory weChatFactory = context.getBean("weChatPaymentFactory", PaymentFactory.class);
        Payment weChatPay = weChatFactory.createPayment();
        weChatPay.pay(399.9);

        // 3. 获取支付宝支付工厂
        PaymentFactory alipayFactory = context.getBean("alipayPaymentFactory", PaymentFactory.class);
        Payment alipay = alipayFactory.createPayment();
        System.out.println(alipay.queryPayStatus("ALIPAY20260415002"));
    }
}

进阶要点(企业开发避坑)

  • 实际开发中,具体产品(如WeChatPayment)也可交给Spring管理,在具体工厂中通过@Autowired注入,而非手动new,进一步降低耦合;

  • 可通过Spring的@Profile注解,实现不同环境(开发、测试、生产)切换不同的工厂(如开发环境用微信支付工厂,生产环境用支付宝支付工厂);

  • 工厂方法模式常与单例模式结合,Spring的bean默认是单例,无需额外实现单例,确保工厂实例唯一,节省资源;

  • 若Spring XML配置出现链接失效,优先使用注解配置,或替换为本地XSD文件,避免因外部链接问题导致项目启动失败。

五、实战三:框架中的工厂方法模式(面试必说)

工厂方法模式在Java主流框架中应用极其广泛,面试时能说出1-2个具体应用,会显得你理解更深刻,不是只懂理论。

1. Spring 框架:FactoryBean(核心应用)

Spring中的FactoryBean是工厂方法模式的经典实现,也是Spring容器中创建复杂Bean的核心机制。

  • Product(抽象产品):Object(所有Bean的父类);

  • ConcreteProduct(具体产品):Spring管理的复杂Bean(如DataSource、SqlSessionFactory);

  • Factory(抽象工厂):FactoryBean接口(定义了getObject()方法,用于创建Bean);

  • ConcreteFactory(具体工厂):所有实现FactoryBean的类(如SqlSessionFactoryBean)。

示例(MyBatis与Spring整合时的FactoryBean应用):

java 复制代码
// SqlSessionFactoryBean 是具体工厂,实现了FactoryBean接口
// 它的getObject()方法负责创建SqlSessionFactory(具体产品)
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource);
    return factoryBean.getObject(); // 调用工厂方法创建产品
}

2. MyBatis 框架:MapperFactoryBean

MyBatis中的MapperFactoryBean用于创建Mapper接口的代理对象,本质也是工厂方法模式的实现。

  • 抽象产品:Mapper接口(如UserMapper);

  • 具体产品:Mapper接口的代理对象(MyBatis动态生成);

  • 抽象工厂:FactoryBean接口;

  • 具体工厂:MapperFactoryBean(负责创建Mapper代理对象)。

3. 实际项目场景:日志框架适配

项目中需要适配不同的日志框架(Log4j、SLF4J),每个日志框架对应一个具体产品,每个产品对应一个具体工厂,用工厂方法模式统一管理,新增日志框架时无需修改原有业务代码。

六、工厂方法模式面试高频考点(必背)

1. 工厂方法模式的核心优势?

  • 解耦:屏蔽具体产品的创建细节,客户端只依赖抽象接口,降低代码耦合度;

  • 遵循开闭原则:新增产品时,只需新增具体产品和具体工厂,无需修改原有代码;

  • 职责单一:每个具体工厂只负责一个产品的创建,避免工厂类职责过重,便于维护;

  • 扩展性强:可灵活扩展产品类型,无需改动客户端和原有工厂代码。

2. 工厂方法模式和简单工厂模式的区别?(高频中的高频)

核心区别:工厂的职责划分和是否遵循开闭原则,用表格清晰区分,面试直接说清:

对比维度 工厂方法模式 简单工厂模式
工厂数量 与具体产品数量一致(一个产品一个工厂) 只有一个工厂(负责所有产品创建)
工厂职责 单一职责,一个工厂只创建一个产品 职责过重,一个工厂创建所有产品
开闭原则 符合(新增产品只需新增工厂) 不符合(新增产品需修改工厂逻辑)
耦合度 低(客户端依赖抽象接口) 高(客户端依赖具体工厂)
适用场景 产品类型多、需要灵活扩展 产品类型少、无需频繁扩展

3. 工厂方法模式和抽象工厂模式的区别?(面试易错点)

核心区别:关注焦点不同,记住一句话即可:

  • 工厂方法模式:关注"单一产品",一个工厂造一个产品(如微信支付工厂只造微信支付);

  • 抽象工厂模式:关注"产品族",一个工厂造一个系列的产品(如微信工厂造微信支付、微信退款、微信对账)。

补充:工厂方法模式是抽象工厂模式的基础,抽象工厂模式可看作是"多个工厂方法模式的组合"。

4. 工厂方法模式的缺点?(避坑重点)

  • 类数量增多:每个具体产品对应一个具体工厂,当产品数量较多时,会产生大量的工厂类,增加系统复杂度;

  • 逻辑冗余:若多个具体工厂的创建逻辑相似,会产生重复代码(可通过抽象类提取公共逻辑解决);

  • 扩展性局限:新增产品等级(如支付产品新增"对账"功能)时,需要修改抽象产品和所有具体产品,违反开闭原则。

5. 什么时候用工厂方法模式?(实战判断)

  • 系统中存在"单一类型、多具体实现"的产品(如多种支付方式、多种日志实现、多种数据库连接);

  • 需要灵活扩展产品类型,新增产品时不想修改原有代码,遵循开闭原则;

  • 希望屏蔽产品创建细节,降低客户端与具体产品的耦合度;

  • 产品创建逻辑复杂(如需要初始化参数、调用第三方SDK),需要统一封装创建逻辑。

七、总结(实战+面试双达标)

对于Java后端开发者来说,工厂方法模式是"入门级但高频使用"的设计模式,它没有抽象工厂模式复杂,但却是理解抽象工厂、Spring FactoryBean等高级特性的基础。

  1. 基础:掌握4个核心角色,理解"一个工厂造一个产品"的核心思想,能手动实现简单的工厂方法模式;

  2. 实战:结合Spring框架,学会用依赖注入管理工厂和产品,解决实际开发中的耦合问题,避开配置文件链接失效的坑;

  3. 面试:能清晰区分工厂方法与简单工厂、抽象工厂的区别,说出框架中的应用场景,掌握其优缺点和适用场景;

  4. 避坑:记住"产品多就用工厂方法,产品族多就用抽象工厂",避免过度设计(如产品数量少,用简单工厂更简洁)。

记住一句话:工厂方法模式的核心是"拆分职责、遵循开闭、降低耦合",用抽象接口隔离具体实现,让扩展更灵活。掌握它,能让你的代码更规范、更易维护,面试时也能轻松应对设计模式相关提问。

补充:常见问题解决

  • 问题1:工厂类数量过多,如何简化?

    解决:提取所有具体工厂的公共逻辑到抽象工厂类(抽象类),具体工厂继承抽象类,减少重复代码;或结合Spring依赖注入,用注解简化工厂创建。

  • 问题2:Spring XML配置中出现"link dead"(链接失效)怎么办?

    解决:优先使用注解配置(@Component、@Bean);若必须用XML,替换为本地Spring XSD文件,或升级Spring依赖版本,确保链接可访问。

  • 问题3:如何结合单例模式使用工厂方法?

    解决:将具体工厂交给Spring管理(Spring bean默认单例);或在具体工厂中实现单例模式(如饿汉式),确保工厂实例唯一。

相关推荐
田梓燊2 小时前
leetcode 142
android·java·leetcode
wjs20242 小时前
C++ 基本的输入输出
开发语言
亚空间仓鼠2 小时前
Ansible之Playbook(三):变量应用
java·前端·ansible
码路飞2 小时前
昨天还在发 Qwen3.5,今天技术负责人就被阿里云赶走了
java·javascript
程序员老邢2 小时前
【技术底稿 15】SpringBoot 异步文件上传实战:多线程池隔离 + 失败重试 + 实时状态推送
java·经验分享·spring boot·后端·程序人生·spring
码云数智-园园2 小时前
Python的GIL锁如何影响多线程性能?有哪些替代方案?
开发语言
咬_咬2 小时前
go语言学习(map)
开发语言·学习·golang·map
古城小栈2 小时前
rustup 命令工具,掌控 Rust 开发环境
开发语言·后端·rust
lly2024062 小时前
NumPy 高级索引
开发语言