Java 工厂方法模式从入门到实战(后端必看)
前言:工厂方法模式是Java创建型设计模式的基础,也是开发者接触最多、最易上手的设计模式之一。它解决了简单工厂模式"职责过重、违反开闭原则"的核心痛点,在日常开发(如接口适配、组件实例化)和框架底层(Spring、MyBatis)中应用广泛。很多开发者分不清它与简单工厂、抽象工厂的区别,面试时只能说清表面用法,无法结合实战场景阐述。本文从入门到实战,结合真实业务场景+可运行代码+面试高频考点,带你吃透工厂方法模式,看完直接能用、能说、能面试,新手也能快速上手。
一、为什么需要工厂方法模式?(痛点直击)
在学习工厂方法模式前,先想一个Java开发中最常见的场景:假设你开发一个电商系统的支付模块,初期只支持微信支付,后来需要新增支付宝支付、银联支付,你会怎么写代码?
用简单工厂模式的弊端的很明显:
-
所有支付方式的创建逻辑都写在一个工厂类中,随着支付方式增多,工厂类代码会越来越臃肿,职责过重,难以维护;
-
新增支付方式(如银联支付)时,需要修改工厂类的判断逻辑,违反"开闭原则"(对扩展开放、对修改关闭);
-
代码耦合度高,客户端需要知道具体支付类的名称,一旦类名修改,客户端代码也需要同步修改。
而工厂方法模式的核心价值,就是解决上述问题------将具体产品的创建逻辑拆分到对应工厂,让每个工厂只负责一个产品的创建,新增产品时只需新增工厂,无需修改原有代码。
核心结论:当系统中存在"单一类型、多具体实现"的产品(如多种支付方式、多种日志实现),且需要灵活扩展、降低耦合时,就用工厂方法模式。
二、工厂方法模式核心概念(极简理解)
工厂方法模式的核心思想:"定义一个创建产品的抽象工厂接口,每个具体产品对应一个具体工厂,具体工厂实现抽象工厂的方法,负责创建对应的具体产品"。
简单说,工厂方法模式是"一个工厂造一个产品",区别于抽象工厂的"一个工厂造一个系列产品"、简单工厂的"一个工厂造所有产品"。
工厂方法模式有4个核心角色(理论结合实战,重点记实战用法,避免死记硬背):
-
Product(抽象产品):定义所有具体产品的统一接口,规范产品的核心行为(如支付接口、日志接口),所有具体产品都必须实现这个接口;
-
ConcreteProduct(具体产品):实现抽象产品接口,是工厂方法模式最终创建的实例(如微信支付、支付宝支付、Log4j日志);
-
Factory(抽象工厂):核心接口,定义一个创建抽象产品的方法(如创建支付的方法),具体工厂必须实现这个接口;
-
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等高级特性的基础。
-
基础:掌握4个核心角色,理解"一个工厂造一个产品"的核心思想,能手动实现简单的工厂方法模式;
-
实战:结合Spring框架,学会用依赖注入管理工厂和产品,解决实际开发中的耦合问题,避开配置文件链接失效的坑;
-
面试:能清晰区分工厂方法与简单工厂、抽象工厂的区别,说出框架中的应用场景,掌握其优缺点和适用场景;
-
避坑:记住"产品多就用工厂方法,产品族多就用抽象工厂",避免过度设计(如产品数量少,用简单工厂更简洁)。
记住一句话:工厂方法模式的核心是"拆分职责、遵循开闭、降低耦合",用抽象接口隔离具体实现,让扩展更灵活。掌握它,能让你的代码更规范、更易维护,面试时也能轻松应对设计模式相关提问。
补充:常见问题解决
-
问题1:工厂类数量过多,如何简化?
解决:提取所有具体工厂的公共逻辑到抽象工厂类(抽象类),具体工厂继承抽象类,减少重复代码;或结合Spring依赖注入,用注解简化工厂创建。
-
问题2:Spring XML配置中出现"link dead"(链接失效)怎么办?
解决:优先使用注解配置(@Component、@Bean);若必须用XML,替换为本地Spring XSD文件,或升级Spring依赖版本,确保链接可访问。
-
问题3:如何结合单例模式使用工厂方法?
解决:将具体工厂交给Spring管理(Spring bean默认单例);或在具体工厂中实现单例模式(如饿汉式),确保工厂实例唯一。