工厂方法模式:日志记录器工厂
说实话,工厂方法模式是我在实际项目中用得最多的创建型模式之一。为什么?因为它解决了一个很实在的问题——你不想在代码里写死具体类。
我记得刚入行那会儿,写代码都是直接 new 对象。后来维护一个老系统,发现到处是 new MySQLDriver()、new Log4jLogger()。老板说换 PostgreSQL,我改了一整天的代码。嗯,从那以后,我再也不敢硬编码了。
模式定义:一句话说清楚
工厂方法模式的定义其实很简洁:定义一个创建对象的接口,但让子类决定实例化哪个类。说白了,就是把「new 什么」这件事延迟到子类去处理。
你想想看,如果所有创建逻辑都堆在一个地方,那叫简单工厂。但简单工厂有个毛病——每次加新产品,你都得改那个工厂类。开闭原则就 violated 了。工厂方法模式把创建逻辑拆到各个子工厂里,加新产品只需要加新子类,不用改已有代码。
业务场景:日志记录器工厂
咱们课程里选的场景是「日志记录器工厂」。为什么选这个?因为我在项目中遇到过太多次日志框架切换的问题。
早期项目用 Log4j,后来公司统一要求用 Logback。如果代码里到处都是 Logger.getLogger() 这种 Log4j 的 API,那迁移起来简直要命。工厂方法模式正好解决这个问题——我们定义一个抽象的日志记录器接口,然后让 Log4j 和 Logback 各自实现自己的工厂。
核心结构
先看一张图,把整体结构理清楚:
这张图里,LoggerFactory 是抽象工厂,定义了一个 createLogger() 方法。两个具体工厂各自实现这个方法,返回不同的 Logger 实例。客户端只跟 LoggerFactory 和 Logger 接口打交道,具体是 Log4j 还是 Logback,它根本不知道。
代码实现:一步一步来
先定义抽象产品——Logger 接口:
public interface Logger {
void log(String message);
}
然后是两个具体产品:
public class Log4jLogger implements Logger {
@Override
public void log(String message) {
// 这里调用 Log4j 的原生 API
System.out.println("[Log4j] " + message);
}
}
public class LogbackLogger implements Logger {
@Override
public void log(String message) {
// 这里调用 Logback 的原生 API
System.out.println("[Logback] " + message);
}
}
接下来是抽象工厂:
public abstract class LoggerFactory {
public abstract Logger createLogger();
}
具体工厂实现:
public class Log4jLoggerFactory extends LoggerFactory {
@Override
public Logger createLogger() {
return new Log4jLogger();
}
}
public class LogbackLoggerFactory extends LoggerFactory {
@Override
public Logger createLogger() {
return new LogbackLogger();
}
}
客户端使用:
public class Client {
public static void main(String[] args) {
// 通过配置文件或参数决定用哪个工厂
LoggerFactory factory = new Log4jLoggerFactory();
Logger logger = factory.createLogger();
logger.log("这是一条日志");
}
}
关键点:客户端代码里没有出现 Log4jLogger 或 LogbackLogger 这些具体类名。它只依赖 LoggerFactory 和 Logger 这两个抽象。哪天要切换日志框架,只需要换一个工厂实现,客户端代码一行都不用改。
避坑指南
我曾经在一个项目中犯过这样的错误——工厂方法里做了太多事情。比如在 createLogger() 里不仅创建 Logger,还读取配置文件、初始化连接池。结果每次调用工厂方法都特别慢,而且出了问题很难排查。
记住:工厂方法只负责创建对象。初始化、配置这些事,交给别的方法去做,或者用 Builder 模式来处理。
工厂方法 vs 简单工厂
很多初学者会搞混这两个概念。我简单说一下区别:
| 对比项 | 简单工厂 | 工厂方法 |
|---|---|---|
| 创建逻辑位置 | 集中在一个工厂类 | 分散在各个子工厂 |
| 扩展性 | 加产品要改工厂类 | 加产品只需加子类 |
| 适用场景 | 产品种类少且稳定 | 产品种类多或经常变化 |
说白了,简单工厂适合「你知道不会怎么变」的场景。工厂方法适合「你预感以后会加东西」的场景。我个人习惯是:拿不准的时候,直接用工厂方法。因为从简单工厂重构到工厂方法,改动量其实不小。
实际项目中的应用
我记得去年做一个数据迁移工具,需要支持 MySQL 和 PostgreSQL。如果用简单工厂,每次加数据库都要改工厂类。我用了工厂方法模式,每个数据库驱动对应一个工厂。后来客户说要加 Oracle 支持,我花了半小时写了个 OracleDriverFactory,完事。
再比如日志框架适配,这个场景太常见了。很多公司内部有统一的日志规范,但底层实现可能是 Log4j、Logback 或者 SLF4J。用工厂方法模式,你可以写一个 LoggerFactory 接口,然后每个日志框架一个实现。切换的时候,改一行配置就行。
小技巧:工厂方法模式经常和配置文件结合使用。比如在 application.properties 里写 logger.factory=com.example.Log4jLoggerFactory,然后用反射加载。这样连代码都不用改,改个配置文件就能切换实现。
总结
工厂方法模式的核心价值就一句话:把创建对象的职责从客户端剥离出来,交给专门的子工厂。这样做的好处是:
- 客户端代码只依赖抽象,不依赖具体实现
- 新增产品类型不需要修改已有代码
- 每个工厂职责单一,只负责创建一种产品
嗯,其实设计模式这东西,说白了就是「前人踩过的坑总结出来的经验」。工厂方法模式解决的就是「new 对象太死板」这个问题。你在项目中遇到类似场景,不妨试试这个模式。