工厂方法模式:从简单工厂到真正的工厂
说起工厂方法模式,我得先坦白一件事。我刚开始写代码那会儿,觉得工厂模式就是个花架子——明明 new 一下就能搞定的事,非要绕个弯子。直到有一次,我在一个电商项目里被坑惨了……嗯,从那以后,我再也不敢小看它了。
今天咱们就好好聊聊工厂方法模式。我会从简单工厂讲起,一步步带你看到工厂方法是怎么诞生的,以及它在 JDBC 里是怎么用的。
1. 简单工厂:最朴素的“工厂”
先看一个场景。假设你要写一个日志系统,支持文件日志和数据库日志。最直接的写法是这样的:
// 日志接口
public interface Logger {
void log(String message);
}
// 文件日志
public class FileLogger implements Logger {
public void log(String message) {
System.out.println("写入文件: " + message);
}
}
// 数据库日志
public class DatabaseLogger implements Logger {
public void log(String message) {
System.out.println("写入数据库: " + message);
}
}
那客户端怎么用呢?最笨的办法就是直接 new:
Logger logger = new FileLogger();
logger.log("用户登录了");
问题来了。如果哪天你想把文件日志换成数据库日志,你得改所有 new 的地方。项目小还好说,项目大了,这简直就是噩梦。
于是简单工厂模式登场了:
public class LoggerFactory {
public static Logger createLogger(String type) {
if ("file".equals(type)) {
return new FileLogger();
} else if ("database".equals(type)) {
return new DatabaseLogger();
}
throw new IllegalArgumentException("未知的日志类型");
}
}
// 客户端调用
Logger logger = LoggerFactory.createLogger("file");
logger.log("用户登录了");
这样,客户端只需要知道一个工厂类,不用关心具体实现。想换日志类型?改一下参数就行。
简单工厂的核心:把对象的创建逻辑集中到一个地方。客户端不直接 new,而是通过工厂来获取对象。
2. 简单工厂的痛点
简单工厂好用吗?说实话,小项目里挺好用的。但我在实际项目中遇到过一个问题——开闭原则被破坏了。
什么意思呢?假如现在要加一个“云日志”类型,你得去修改 LoggerFactory 的 createLogger 方法,加一个 else if。每次新增产品,都要改工厂类。这违反了“对扩展开放,对修改关闭”的原则。
而且,随着产品种类增多,这个工厂类会变得臃肿不堪。我曾经接手过一个项目,里面的简单工厂有上百个 if-else,看得我头皮发麻。
避坑指南:我曾经在一个支付系统中用简单工厂处理了十几种支付方式。结果每次新增支付渠道,都要改工厂类,改一次测试一次,烦不胜烦。后来我重构成了工厂方法模式,世界清净了。
3. 工厂方法模式:让子类决定创建什么
工厂方法模式是怎么解决这个问题的?说白了,就是把“创建对象”这个动作延迟到子类去实现。
看代码:
// 抽象工厂
public abstract class LoggerFactory {
public abstract Logger createLogger();
// 公共的业务方法
public void logMessage(String message) {
Logger logger = createLogger();
logger.log(message);
}
}
// 具体工厂 - 文件日志
public class FileLoggerFactory extends LoggerFactory {
public Logger createLogger() {
return new FileLogger();
}
}
// 具体工厂 - 数据库日志
public class DatabaseLoggerFactory extends LoggerFactory {
public Logger createLogger() {
return new DatabaseLogger();
}
}
// 客户端
LoggerFactory factory = new FileLoggerFactory();
factory.logMessage("用户登录了");
看到区别了吗?现在每个产品都有自己对应的工厂。想加“云日志”?直接加一个 CloudLogger 和 CloudLoggerFactory 就行,不用改任何现有代码。
我的习惯:我个人习惯在工厂方法模式中,把公共的业务逻辑放在抽象工厂里。比如上面的 logMessage 方法,所有日志记录前的格式化、后处理都可以放在这里,子类工厂只需要关心“创建什么对象”。
4. 适用场景:什么时候用工厂方法?
不是所有地方都需要工厂方法。我总结了几个典型的场景:
| 场景 | 说明 | 例子 |
|---|---|---|
| 产品种类频繁扩展 | 经常要加新的产品类型 | 支付渠道、消息推送 |
| 产品创建逻辑复杂 | 创建对象需要一堆初始化步骤 | 数据库连接、网络客户端 |
| 客户端不需要知道具体类 | 只关心接口,不关心实现 | 日志系统、缓存系统 |
| 框架设计 | 让框架使用者扩展功能 | Spring BeanFactory |
你想想看,如果你的项目里只有两三种产品,而且基本不会变,那简单工厂完全够用。别为了用模式而用模式,这是很多新手容易犯的毛病。
5. JDBC 中的工厂模式
说到 JDBC,这可是工厂方法模式的经典案例。咱们来看看 JDBC 是怎么用工厂模式的。
JDBC 的核心接口是 java.sql.Connection,但不同的数据库(MySQL、Oracle、PostgreSQL)都有自己的实现。那问题来了:客户端怎么获取到正确的 Connection 实现?
答案就是 DriverManager——它其实就是一个简单工厂:
// 客户端代码
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb",
"root",
"password"
);
但这里有个更深的层次。每个数据库驱动(比如 MySQL 的 com.mysql.cj.jdbc.Driver)都实现了 java.sql.Driver 接口。当驱动被加载时,它会把自己注册到 DriverManager 里。
而 Driver 接口的 connect 方法,本质上就是一个工厂方法:
// 这是 Driver 接口的核心方法
public interface Driver {
Connection connect(String url, Properties info) throws SQLException;
}
// MySQL 的实现
public class Driver implements java.sql.Driver {
public Connection connect(String url, Properties info) {
// 解析 URL,创建 MySQL 的 Connection 实现
return new ConnectionImpl(url, info);
}
}
// Oracle 的实现
public class OracleDriver implements java.sql.Driver {
public Connection connect(String url, Properties info) {
// 解析 URL,创建 Oracle 的 Connection 实现
return new OracleConnection(url, info);
}
}
看到了吗?每个数据库驱动都是一个具体的工厂,负责创建自己的 Connection 对象。这就是工厂方法模式在 JDBC 中的体现。
JDBC 的工厂模式层次:
- 抽象产品:
Connection接口 - 具体产品:
ConnectionImpl、OracleConnection等 - 抽象工厂:
Driver接口 - 具体工厂:
com.mysql.cj.jdbc.Driver、oracle.jdbc.OracleDriver等 - 客户端:通过
DriverManager获取连接
6. 知识体系总览
下面这张图帮你理清工厂方法模式的核心脉络:
7. 总结
工厂方法模式,说白了就是让“创建对象”这件事变得更有弹性。它把变化的点封装起来,让系统更容易扩展。
我个人觉得,理解工厂方法的关键在于想清楚一个问题:谁应该负责创建对象?简单工厂把责任集中在一个类上,工厂方法把责任分散到多个子类上。没有绝对的好坏,只有合不合适。
最后送你一句话:设计模式是工具,不是教条。用对了是神器,用错了是累赘。多在实践中体会,慢慢你就会有感觉了。