在Java开发中,设计模式常常被认为是"理论大于实践"的知识——书本上的示例大多是"创建汽车" "生产电脑"这类简单场景,让人疑惑这些模式在实际框架中到底怎么用。但如果你仔细研究Spring、MyBatis等主流框架的源码,会发现设计模式无处不在,尤其是工厂模式,几乎成了框架设计的"基础设施"。
曾经在维护一个老项目时,遇到过这样的问题:系统需要对接多个支付渠道(支付宝、微信、银联),最初的代码用了大量if-else
判断渠道类型,每次新增渠道都要修改核心逻辑,稍不注意就会影响现有功能。后来用工厂模式重构后,新增渠道只需添加一个实现类和配置,无需修改原有代码,稳定性和扩展性提升明显。这个经历让我意识到,工厂模式的核心价值不在于"创建对象",而在于解耦对象创建与使用,这也是它被框架广泛采用的根本原因。
一、工厂模式的三种形态与框架实践
工厂模式主要有三种形态:简单工厂、工厂方法和抽象工厂。它们并非相互替代,而是分别适用于不同复杂度的场景。
1. 简单工厂:框架中的参数化创建
简单工厂通过一个统一的工厂类,根据参数创建不同类型的对象。这种模式在框架中常用于"根据配置创建实例"的场景,比如日志框架SLF4J对日志实现类的选择。
SLF4J中的简单工厂示例:
// SLF4J获取Logger的核心逻辑(简化版)
public class LoggerFactory {
// 根据类名获取Logger实例
public static Logger getLogger(String name) {
// 实际逻辑会根据配置的日志实现(Logback/Log4j等)创建对应的Logger
ILoggerFactory factory = getILoggerFactory();
return factory.getLogger(name);
}
// 获取日志工厂实例(简单工厂的核心)
private static ILoggerFactory getILoggerFactory() {
// 检查是否有Logback的实现
if (isLogbackAvailable()) {
return new LogbackLoggerFactory();
}
// 检查是否有Log4j的实现
else if (isLog4jAvailable()) {
return new Log4jLoggerFactory();
}
// 默认使用JDK自带日志
else {
return new JDK14LoggerFactory();
}
}
}
这里的LoggerFactory
就是典型的简单工厂:它根据classpath中存在的日志实现类,自动创建对应的Logger
实例,用户无需关心具体实现,只需调用LoggerFactory.getLogger()
即可。
2. 工厂方法:Spring中的Bean创建
工厂方法将对象创建延迟到子类,每个产品对应一个工厂。这种模式在Spring中应用广泛,最典型的就是FactoryBean
接口——它允许开发者自定义Bean的创建逻辑。
Spring中FactoryBean的应用:
// 自定义FactoryBean,创建RedisTemplate实例
public class RedisTemplateFactoryBean implements FactoryBean<RedisTemplate> {
private String host;
private int port;
// 设置Redis连接参数(通过Spring配置注入)
public void setHost(String host) { this.host = host; }
public void setPort(int port) { this.port = port; }
// 工厂方法:创建并配置RedisTemplate
@Override
public RedisTemplate getObject() throws Exception {
RedisTemplate template = new RedisTemplate();
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(host);
factory.setPort(port);
factory.afterPropertiesSet(); // 初始化连接工厂
template.setConnectionFactory(factory);
template.afterPropertiesSet(); // 初始化RedisTemplate
return template;
}
// 返回产品类型
@Override
public Class<?> getObjectType() {
return RedisTemplate.class;
}
}
// Spring配置文件中使用工厂Bean
<bean id="redisTemplate" class="com.example.RedisTemplateFactoryBean">
<property name="host" value="localhost"/>
<property name="port" value="6379"/>
</bean>
通过FactoryBean
,开发者可以将复杂的对象创建逻辑封装在工厂中,Spring容器只需调用getObject()
即可获取实例。这种方式比直接通过构造器创建Bean更灵活,尤其适合需要大量初始化操作的对象。
3. 抽象工厂:MyBatis的数据源适配
抽象工厂用于创建一系列相关或相互依赖的对象,比如不同数据库(MySQL、Oracle)的连接池、语句对象等。MyBatis的DataSource
模块就使用了抽象工厂模式,适配不同的数据库环境。
MyBatis中的抽象工厂简化示例:
// 抽象产品:数据库连接
public interface Connection {
void execute(String sql);
}
// 具体产品:MySQL连接
public class MySQLConnection implements Connection {
@Override
public void execute(String sql) {
System.out.println("MySQL执行SQL:" + sql);
}
}
// 具体产品:Oracle连接
public class OracleConnection implements Connection {
@Override
public void execute(String sql) {
System.out.println("Oracle执行SQL:" + sql);
}
}
// 抽象工厂:数据库产品族工厂
public interface DatabaseFactory {
Connection createConnection();
Statement createStatement(); // 其他相关产品
}
// 具体工厂:MySQL工厂
public class MySQLFactory implements DatabaseFactory {
@Override
public Connection createConnection() {
return new MySQLConnection();
}
@Override
public Statement createStatement() {
return new MySQLStatement();
}
}
// 具体工厂:Oracle工厂
public class OracleFactory implements DatabaseFactory {
@Override
public Connection createConnection() {
return new OracleConnection();
}
@Override
public Statement createStatement() {
return new OracleStatement();
}
}
// MyBatis中根据配置选择工厂
public class DatabaseFactoryBuilder {
public static DatabaseFactory build(String type) {
if ("mysql".equals(type)) {
return new MySQLFactory();
} else if ("oracle".equals(type)) {
return new OracleFactory();
} else {
throw new IllegalArgumentException("不支持的数据库类型");
}
}
}
MyBatis通过这种方式,让用户只需配置数据库类型,就能自动获取对应的连接、语句等对象,无需关心不同数据库的实现差异。
二、工厂模式解决的核心问题
框架设计中,工厂模式主要解决三类问题:
- 屏蔽实现细节
用户只需知道"要什么",无需知道"怎么创建"。比如Spring的BeanFactory
,用户通过getBean()
获取对象,无需关心Bean的初始化过程。 - 应对变化扩展
新增产品时无需修改原有代码。比如Slf4j要支持新的日志框架,只需新增对应的ILoggerFactory
实现,无需修改LoggerFactory
。 - 管理对象生命周期
工厂可以统一管理对象的创建、初始化和销毁。比如Spring的FactoryBean
,可以在getObject()
中处理初始化逻辑,在destroy()
中释放资源。
三、自己实现工厂模式的最佳实践
在实际开发中,我们可以借鉴框架的设计思路,合理使用工厂模式:
1. 简单场景用静态工厂
当产品类型较少且变化不频繁时,用静态工厂更简洁:
// 支付渠道静态工厂
public class PaymentFactory {
// 私有构造器,防止实例化
private PaymentFactory() {}
// 根据渠道类型创建支付实例
public static Payment createPayment(String channel) {
switch (channel) {
case "alipay":
return new AlipayPayment();
case "wechat":
return new WechatPayment();
case "unionpay":
return new UnionpayPayment();
default:
throw new IllegalArgumentException("不支持的支付渠道:" + channel);
}
}
}
// 使用方式
Payment payment = PaymentFactory.createPayment("alipay");
payment.pay(new BigDecimal("100"));
2. 复杂场景用工厂接口+配置
当产品类型较多或需要动态扩展时,结合接口和配置文件(或注解)更灵活:
// 支付渠道工厂接口
public interface PaymentFactory {
Payment create();
String getChannel(); // 返回支持的渠道
}
// 支付宝工厂实现
@Component
public class AlipayFactory implements PaymentFactory {
@Override
public Payment create() {
return new AlipayPayment();
}
@Override
public String getChannel() {
return "alipay";
}
}
// 支付工厂管理器(自动发现所有工厂)
@Component
public class PaymentFactoryManager {
private final Map<String, PaymentFactory> factories = new HashMap<>();
// Spring自动注入所有PaymentFactory实现
public PaymentFactoryManager(List<PaymentFactory> factoryList) {
for (PaymentFactory factory : factoryList) {
factories.put(factory.getChannel(), factory);
}
}
// 根据渠道获取支付实例
public Payment getPayment(String channel) {
PaymentFactory factory = factories.get(channel);
if (factory == null) {
throw new IllegalArgumentException("不支持的支付渠道:" + channel);
}
return factory.create();
}
}
这种方式下,新增支付渠道只需添加PaymentFactory
实现类并标注@Component
,无需修改现有代码,完全符合"开闭原则"。
四、工厂模式的使用误区
- 过度设计
不要为简单的对象创建强行引入工厂模式。如果一个对象通过new
关键字就能轻松创建,且未来不会有变化,直接实例化更合适。 - 工厂职责过重
避免在工厂中加入复杂的业务逻辑。工厂的核心职责是"创建对象",业务逻辑应放在产品类或其他服务中。 - 忽略默认实现
框架通常会提供默认工厂实现(如Spring的DefaultListableBeanFactory
),大多数情况下,直接使用或扩展默认实现比从零开始更高效。
总结
工厂模式在框架中的应用,本质上是对"对象创建"这一行为的规范化和抽象化。从Spring的BeanFactory
到MyBatis的数据源工厂,再到SLF4J的日志工厂,它们都遵循同一个设计理念:将对象的创建与使用分离,让系统更灵活、更易于扩展。
在实际开发中,我们不必拘泥于工厂模式的三种形态,而应理解其"解耦"的核心思想。当发现代码中出现大量new
关键字、if-else
类型判断,或者需要频繁新增相似类型的对象时,不妨考虑用工厂模式重构——就像那些优秀的框架一样,让代码在变化中保持稳定与优雅。