1. 工厂方法模式深度解析
工厂方法模式是我在多年软件开发实践中使用频率最高的设计模式之一。它完美体现了"面向接口编程"和"开闭原则"的思想,让代码在面对变化时展现出惊人的弹性。记得我第一次在电商支付系统中应用这个模式时,原本需要三天才能接入的新支付方式,现在只需要两小时就能完成。
1.1 模式本质与价值
工厂方法模式的核心在于将对象的创建过程抽象化。当我们需要创建一个对象,但又不能或不希望指定具体的类时,这个模式就派上用场了。它通过定义一个创建对象的接口,但让子类决定实例化哪个类,实现了创建逻辑的延迟绑定。
关键理解:工厂方法模式不是用来替代new关键字的,而是用来管理那些需要灵活创建的对象的。
在实际项目中,我发现这个模式特别适合以下场景:
- 系统需要支持多种实现变体
- 创建逻辑可能随时间变化
- 需要将产品创建与使用解耦
- 需要提供扩展点让其他开发者自定义实现
1.2 模式结构详解
让我们更深入地看看工厂方法模式的各个组成部分:
1.2.1 产品接口(Product)
这是所有具体产品的父类或接口,定义了产品的公共方法。设计时要注意:
- 接口应该足够抽象,能涵盖所有变体的共性
- 但也不能过于宽泛,否则实现类会难以遵循
- 通常包含3-5个核心方法为宜
1.2.2 具体产品(Concrete Product)
实现产品接口的具体类。每个具体产品都应该:
- 完全实现产品接口的所有方法
- 可以有自己的特有方法和属性
- 通常与具体工厂一一对应
1.2.3 创建者接口(Creator)
声明工厂方法的抽象类或接口。关键点:
- 可以只声明工厂方法,也可以提供默认实现
- 可以包含与产品相关的业务逻辑(模板方法)
- 不应该知道具体产品的细节
1.2.4 具体创建者(Concrete Creator)
实现或重写工厂方法的具体类。注意:
- 每个具体创建者通常只负责创建一个具体产品
- 可以包含产品初始化逻辑
- 可以重写父类的其他方法以改变行为
2. 实战:构建可扩展的日志系统
让我们通过一个更完整的日志系统示例,展示工厂方法模式的实际应用。这个系统需要支持控制台、文件和数据库三种日志方式,并且要易于扩展新的日志类型。
2.1 产品接口设计
首先定义日志记录器的抽象接口:
java复制/**
* 日志记录器接口
* 设计要点:
* 1. 包含日志记录的基本操作
* 2. 考虑线程安全性
* 3. 支持不同日志级别
*/
public interface Logger {
void debug(String message);
void info(String message);
void warn(String message);
void error(String message, Throwable t);
/**
* 是否启用某个日志级别
*/
boolean isEnabled(LogLevel level);
/**
* 设置日志级别过滤器
*/
void setFilter(LogFilter filter);
}
2.2 具体产品实现
以文件日志记录器为例,展示一个更完整的实现:
java复制public class FileLogger implements Logger {
private final Path logPath;
private LogFilter filter;
private final Lock writeLock = new ReentrantLock();
public FileLogger(Path logPath) {
this.logPath = logPath;
createLogFileIfNotExists();
}
private void createLogFileIfNotExists() {
// 文件创建逻辑...
}
@Override
public void info(String message) {
if (!isEnabled(LogLevel.INFO)) return;
writeLock.lock();
try {
Files.writeString(logPath,
formatLogMessage(LogLevel.INFO, message),
StandardOpenOption.APPEND);
} finally {
writeLock.unlock();
}
}
// 其他方法实现...
}
2.3 工厂类设计
抽象工厂不仅包含创建方法,还可以实现模板方法:
java复制public abstract class LoggerFactory {
private final String name;
protected LoggerFactory(String name) {
this.name = name;
}
/**
* 工厂方法 - 由子类实现
*/
public abstract Logger createLogger();
/**
* 模板方法 - 记录错误的标准流程
*/
public final void logError(String message, Throwable error) {
Logger logger = createLogger();
if (logger.isEnabled(LogLevel.ERROR)) {
logger.error(message, error);
sendAlert(message); // 错误时发送警报
}
}
protected void sendAlert(String message) {
// 默认警报实现...
}
}
2.4 具体工厂实现
文件日志工厂需要处理文件路径等配置:
java复制public class FileLoggerFactory extends LoggerFactory {
private final Path logDirectory;
public FileLoggerFactory(String name, Path logDirectory) {
super(name);
this.logDirectory = logDirectory;
}
@Override
public Logger createLogger() {
Path logFile = logDirectory.resolve(getName() + ".log");
return new FileLogger(logFile);
}
@Override
protected void sendAlert(String message) {
// 文件日志特有的警报逻辑
}
}
3. 高级应用技巧
3.1 参数化工厂方法
有时创建对象需要额外参数,可以扩展工厂方法:
java复制public abstract class LoggerFactory {
public abstract Logger createLogger(LoggerConfig config);
}
public class FileLoggerFactory extends LoggerFactory {
@Override
public Logger createLogger(LoggerConfig config) {
FileLoggerConfig fileConfig = (FileLoggerConfig) config;
return new FileLogger(fileConfig.getPath(), fileConfig.getMaxSize());
}
}
3.2 工厂方法与其他模式结合
3.2.1 与单例模式结合
确保每个工厂实例只创建一个产品实例:
java复制public class DatabaseLoggerFactory extends LoggerFactory {
private volatile Logger instance;
@Override
public Logger createLogger() {
if (instance == null) {
synchronized (this) {
if (instance == null) {
instance = new DatabaseLogger();
}
}
}
return instance;
}
}
3.2.2 与策略模式结合
让工厂创建的对象可以动态改变行为:
java复制public interface LoggingStrategy {
void execute(String message);
}
public class AsyncLoggingStrategy implements LoggingStrategy {
private final Executor executor = Executors.newSingleThreadExecutor();
@Override
public void execute(String message) {
executor.execute(() -> actualLogging(message));
}
// ...
}
public class LoggerFactory {
public Logger createLogger(LoggingStrategy strategy) {
Logger logger = createLogger();
logger.setStrategy(strategy);
return logger;
}
}
3.3 延迟初始化与缓存
对于创建成本高的对象,可以在工厂中实现缓存:
java复制public abstract class LoggerFactory {
private final Map<String, Logger> cache = new ConcurrentHashMap<>();
public Logger getLogger(String name) {
return cache.computeIfAbsent(name, n -> createLogger(n));
}
protected abstract Logger createLogger(String name);
}
4. 性能优化与最佳实践
4.1 对象池技术
对于频繁创建销毁的对象,可以使用对象池:
java复制public class DatabaseLoggerFactory extends LoggerFactory {
private final ObjectPool<DatabaseLogger> pool;
public DatabaseLoggerFactory(int poolSize) {
this.pool = new ObjectPool<>(poolSize, () -> {
return new DatabaseLogger(createConnection());
});
}
@Override
public Logger createLogger() {
return pool.borrowObject();
}
public void returnLogger(DatabaseLogger logger) {
pool.returnObject(logger);
}
}
4.2 依赖注入集成
与现代DI框架结合使用:
java复制@Configuration
public class LoggerConfig {
@Bean
@Scope("prototype")
public Logger fileLogger(@Value("${log.path}") String path) {
return new FileLogger(Paths.get(path));
}
@Bean
public LoggerFactory fileLoggerFactory() {
return new LoggerFactory() {
@Override
public Logger createLogger() {
return fileLogger();
}
};
}
}
4.3 测试策略
针对工厂模式的特定测试方法:
java复制public class LoggerFactoryTest {
@Test
void testFileLoggerFactory() {
LoggerFactory factory = new FileLoggerFactory(tempDir);
Logger logger1 = factory.createLogger();
Logger logger2 = factory.createLogger();
assertNotSame(logger1, logger2); // 通常每次创建新实例
assertTrue(logger1 instanceof FileLogger);
}
@Test
void testLogErrorTemplateMethod() {
TestLoggerFactory factory = new TestLoggerFactory();
factory.logError("Test", new RuntimeException());
assertTrue(factory.getCreatedLogger().errorOccurred());
}
private static class TestLoggerFactory extends LoggerFactory {
private TestLogger createdLogger;
@Override
public Logger createLogger() {
createdLogger = new TestLogger();
return createdLogger;
}
TestLogger getCreatedLogger() {
return createdLogger;
}
}
}
5. 常见问题与解决方案
5.1 循环依赖问题
当工厂和产品相互引用时:
java复制// 错误示例
public class ProductA {
private final Factory factory;
public ProductA(Factory factory) {
this.factory = factory;
}
}
// 解决方案:分离接口
public interface IProduct {
void operation();
}
public class ProductA implements IProduct {
// 不再依赖工厂
}
5.2 过度设计警示
不应该使用工厂方法模式的场景:
- 对象创建逻辑非常简单且不会变化
- 项目中只有1-2个具体产品
- 性能要求极高的场景(直接new更快)
5.3 扩展性问题处理
当产品接口需要变化时:
- 首先考虑是否可以通过默认方法扩展接口
- 如果必须修改接口,创建新的接口版本
- 使用适配器模式兼容旧实现
java复制public interface LoggerV2 extends Logger {
void trace(String message);
default void metric(String name, double value) {
// 默认实现...
}
}
6. 模式对比与选型指南
6.1 工厂方法 vs 简单工厂
| 特性 | 工厂方法 | 简单工厂 |
|---|---|---|
| 扩展性 | 高(新增工厂类) | 低(修改工厂方法) |
| 复杂度 | 较高 | 低 |
| 符合OCP | 是 | 否 |
| 适用场景 | 产品类型多且可能扩展 | 产品类型固定 |
6.2 工厂方法 vs 抽象工厂
| 特性 | 工厂方法 | 抽象工厂 |
|---|---|---|
| 关注点 | 单个产品等级结构 | 多个产品族 |
| 扩展维度 | 垂直(新增产品类型) | 水平(新增产品族) |
| 实现难度 | 较简单 | 较复杂 |
| 典型应用 | 日志记录器 | GUI组件库 |
6.3 何时选择工厂方法
优先考虑工厂方法模式当:
- 系统需要独立于其产品的创建、组合和表示方式
- 系统需要配置多个产品系列中的一个
- 需要强调设计可扩展性
- 需要将产品知识局部化
7. 真实案例分享
在我参与的一个跨国电商平台项目中,我们使用工厂方法模式实现了多通道通知系统:
java复制public interface Notification {
void send(String recipient, String message);
}
public abstract class NotificationFactory {
public abstract Notification createNotification();
public void sendSystemAlert(String message) {
Notification notification = createNotification();
notification.send("sysadmin@company.com", message);
}
}
// 使用示例
NotificationFactory factory = getFactoryBasedOnConfig();
factory.sendSystemAlert("Server CPU overload!");
这个设计带来的好处:
- 新增通知渠道只需添加新工厂类
- 业务代码完全不用修改
- 可以运行时动态切换通知方式
- 方便进行单元测试
8. 设计演进与反思
在实践中,我总结了工厂方法模式的几个演进阶段:
- 初级阶段:简单实现,每个工厂只创建一个产品
- 中级阶段:引入参数化工厂方法和缓存机制
- 高级阶段:与其他模式结合,实现更灵活的创建逻辑
- 反思阶段:识别过度设计,只在真正需要时使用
一个常见的误区是在不应该使用工厂方法的地方强行应用。我曾经在一个性能关键路径上错误地使用了工厂方法,导致系统吞吐量下降了15%。后来改用直接实例化解决了问题。
经验法则:当你在思考"是否应该用工厂方法"时,先问问这个创建逻辑真的需要灵活变化吗?如果答案不确定,从简单开始。