4. 工厂模式(上):简单工厂模式、工厂方法模式、工厂模式在日志框架中的应用
工厂模式,说白了就是帮你「创建对象」的。很多新手觉得这玩意儿多余——new 一下不就完事了吗?我刚开始也这么想。直到有一次我在项目中重构一个支付模块,发现到处都是 new Alipay()、new WechatPay(),改一个支付方式要改十几个文件……嗯,从那以后我再也不敢小看工厂模式了。
4.1 为什么需要工厂模式?
你想想看,软件开发里最怕什么?最怕「变化」。今天用 MySQL,明天换 PostgreSQL;今天对接支付宝,明天加微信支付。如果每次变化都要改业务代码,那代码就成了一团乱麻。
工厂模式的核心思想就一句话:把创建对象的逻辑抽出来,交给专门的「工厂」去管。业务代码只管用,不管造。
- 解耦:调用方不依赖具体类,只依赖接口
- 复用:创建逻辑集中管理,一处改处处生效
- 扩展:加新产品不用改旧代码,符合开闭原则
4.2 简单工厂模式
简单工厂模式,其实不算 GoF 的 23 种设计模式之一。但它是最直观、最常用的入门模式。我个人习惯把它叫做「静态工厂」,因为通常用一个静态方法来创建对象。
4.2.1 结构
简单工厂由三部分组成:
- 工厂类:负责创建产品,通常有一个静态方法
- 产品接口:所有产品共同实现的接口
- 具体产品:实现产品接口的各个类
4.2.2 代码示例
假设我们要做一个日志记录器。先定义产品接口:
// 产品接口
public interface Logger {
void log(String message);
}
// 具体产品:文件日志
public class FileLogger implements Logger {
@Override
public void log(String message) {
System.out.println("写入文件:" + message);
}
}
// 具体产品:数据库日志
public class DatabaseLogger implements Logger {
@Override
public void log(String message) {
System.out.println("写入数据库:" + message);
}
}
然后写一个简单工厂:
// 简单工厂
public class LoggerFactory {
public static Logger createLogger(String type) {
if ("file".equalsIgnoreCase(type)) {
return new FileLogger();
} else if ("database".equalsIgnoreCase(type)) {
return new DatabaseLogger();
} else {
throw new IllegalArgumentException("不支持的日志类型:" + type);
}
}
}
调用方就清爽多了:
Logger logger = LoggerFactory.createLogger("file");
logger.log("用户登录成功");
4.2.3 简单工厂的缺点
简单工厂最大的问题是什么?违背开闭原则。每次加新产品,都要改工厂类的 if-else。我曾经在一个项目里看到工厂类有 20 多个 if-else,改一个不小心就影响其他分支。
4.3 工厂方法模式
工厂方法模式,说白了就是把工厂也抽象化。每个产品对应一个工厂,加新产品就加新工厂,不用改旧代码。
4.3.1 结构
工厂方法模式有四个角色:
- 抽象工厂:定义创建产品的接口
- 具体工厂:实现抽象工厂,创建具体产品
- 抽象产品:产品接口
- 具体产品:实现产品接口
4.3.2 代码示例
还是日志的例子,这次用工厂方法:
// 抽象工厂
public interface LoggerFactory {
Logger createLogger();
}
// 具体工厂:文件日志工厂
public class FileLoggerFactory implements LoggerFactory {
@Override
public Logger createLogger() {
return new FileLogger();
}
}
// 具体工厂:数据库日志工厂
public class DatabaseLoggerFactory implements LoggerFactory {
@Override
public Logger createLogger() {
return new DatabaseLogger();
}
}
调用方:
LoggerFactory factory = new FileLoggerFactory();
Logger logger = factory.createLogger();
logger.log("用户登录成功");
4.3.3 工厂方法的好处
加新产品时,只需要:
- 写一个新的具体产品类
- 写一个对应的具体工厂类
旧代码一行都不用改。这就是开闭原则的体现——对扩展开放,对修改关闭。
我记得有一次项目里要加一个「阿里云日志」的输出方式。用工厂方法,我花了 10 分钟写了个 AliyunLogger 和 AliyunLoggerFactory,然后配置一下注入就完事了。同事看了直呼「优雅」。
4.4 工厂模式在日志框架中的应用
说到日志框架,Java 领域最经典的就是 SLF4J + Logback 的组合。SLF4J 本身就是一个典型的工厂模式应用。
4.4.1 SLF4J 的工厂设计
SLF4J 的核心接口是 Logger,获取 Logger 的方式是:
Logger logger = LoggerFactory.getLogger(MyClass.class);
这里的 LoggerFactory 就是一个工厂类。但它不是简单工厂,也不是纯粹的工厂方法——它用的是绑定模式(Binding),本质上是一种变种的工厂方法。
SLF4J 在编译期不绑定具体实现,运行时通过类路径上的 jar 包来决定用 Logback 还是 Log4j。具体流程:
LoggerFactory.getLogger()调用- 通过
StaticLoggerBinder查找具体实现 - 返回对应的 Logger 实例
4.4.2 自己实现一个迷你日志工厂
为了加深理解,我写一个迷你版的日志工厂:
// 抽象产品
public interface Logger {
void info(String msg);
void error(String msg);
}
// 具体产品:控制台日志
public class ConsoleLogger implements Logger {
@Override
public void info(String msg) {
System.out.println("[INFO] " + msg);
}
@Override
public void error(String msg) {
System.err.println("[ERROR] " + msg);
}
}
// 具体产品:文件日志
public class FileLogger implements Logger {
@Override
public void info(String msg) {
// 写入文件
}
@Override
public void error(String msg) {
// 写入文件
}
}
// 工厂方法模式
public interface LoggerFactory {
Logger createLogger();
}
public class ConsoleLoggerFactory implements LoggerFactory {
@Override
public Logger createLogger() {
return new ConsoleLogger();
}
}
public class FileLoggerFactory implements LoggerFactory {
@Override
public Logger createLogger() {
return new FileLogger();
}
}
调用方通过配置决定用哪个工厂:
// 从配置文件读取工厂类名
String factoryClass = config.getProperty("logger.factory");
LoggerFactory factory = (LoggerFactory) Class.forName(factoryClass).newInstance();
Logger logger = factory.createLogger();
logger.info("系统启动完成");
4.5 简单工厂 vs 工厂方法:怎么选?
| 对比维度 | 简单工厂 | 工厂方法 |
|---|---|---|
| 复杂度 | 低,一个类搞定 | 中,需要多个工厂类 |
| 扩展性 | 差,加产品要改工厂 | 好,加产品加新工厂 |
| 适用场景 | 产品少、变化少 | 产品多、变化频繁 |
| 开闭原则 | 违反 | 符合 |
我个人习惯是:先简单,再升级。刚开始产品只有两三个,用简单工厂快速搞定。等产品多了,或者发现频繁改工厂类,再重构到工厂方法。别一开始就上工厂方法,过度设计也是坑。
4.6 本章小结
工厂模式的核心,就是把「创建对象」这件事从业务代码中剥离出来。简单工厂适合小规模场景,工厂方法适合需要频繁扩展的场景。日志框架 SLF4J 是工厂模式在工业界的经典应用,值得反复学习。
嗯,工厂模式的上半部分就到这里。记住一句话:别在业务代码里 new 具体类,把创建权交给工厂。这是你从「会用」到「会设计」的第一步。