文章目录
-
- 什么是工厂方法模式?
- 核心思想
- 模式结构
- 完整示例:日志记录系统
-
- [1. 定义抽象产品](#1. 定义抽象产品)
- [2. 实现具体产品](#2. 实现具体产品)
- [3. 定义抽象工厂](#3. 定义抽象工厂)
- [4. 实现具体工厂](#4. 实现具体工厂)
- [5. 客户端使用示例](#5. 客户端使用示例)
- [6. 进阶示例:配置化的日志工厂](#6. 进阶示例:配置化的日志工厂)
- 工厂方法模式的优点
-
- [1. 符合开闭原则](#1. 符合开闭原则)
- [2. 客户端与具体产品解耦](#2. 客户端与具体产品解耦)
- [3. 提高代码的可测试性](#3. 提高代码的可测试性)
- 工厂方法模式的缺点
-
- [1. 类的数量增加](#1. 类的数量增加)
- [2. 增加了系统的复杂性](#2. 增加了系统的复杂性)
- 适用场景
- 与简单工厂模式的对比
- 最佳实践
-
- [1. 使用依赖注入](#1. 使用依赖注入)
- [2. 结合配置文件](#2. 结合配置文件)
- [3. 使用泛型增强类型安全](#3. 使用泛型增强类型安全)
- 总结
什么是工厂方法模式?
工厂方法模式(Factory Method Pattern)是一种经典的创建型设计模式,它定义了一个创建对象的接口,但让子类决定要实例化哪一个类。工厂方法让类的实例化推迟到子类,完美遵循了"开闭原则",实现了对象创建的可扩展性。
核心思想
工厂方法模式的核心是:定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化延迟到子类进行。
模式结构
工厂方法模式包含四个核心角色:
- 抽象产品(Product):定义产品的接口
- 具体产品(Concrete Product):实现抽象产品接口的具体类
- 抽象工厂(Creator):声明工厂方法,返回产品对象
- 具体工厂(Concrete Creator):重写工厂方法,返回具体产品实例
完整示例:日志记录系统
让我们通过一个完整的日志记录系统来深入理解工厂方法模式。
1. 定义抽象产品
java
/**
* 日志记录器接口 - 抽象产品角色
* 定义日志记录的基本操作
*/
public interface Logger {
/**
* 记录信息日志
* @param message 日志信息
*/
void info(String message);
/**
* 记录错误日志
* @param message 错误信息
*/
void error(String message);
/**
* 记录警告日志
* @param message 警告信息
*/
void warn(String message);
/**
* 记录调试日志
* @param message 调试信息
*/
void debug(String message);
}
2. 实现具体产品
java
/**
* 文件日志记录器 - 具体产品角色
* 将日志记录到文件中
*/
public class FileLogger implements Logger {
private String filePath;
public FileLogger(String filePath) {
this.filePath = filePath;
System.out.println("初始化文件日志记录器,文件路径: " + filePath);
}
@Override
public void info(String message) {
String log = String.format("[INFO] %s %s", getTimestamp(), message);
writeToFile(log);
}
@Override
public void error(String message) {
String log = String.format("[ERROR] %s %s", getTimestamp(), message);
writeToFile(log);
}
@Override
public void warn(String message) {
String log = String.format("[WARN] %s %s", getTimestamp(), message);
writeToFile(log);
}
@Override
public void debug(String message) {
String log = String.format("[DEBUG] %s %s", getTimestamp(), message);
writeToFile(log);
}
private void writeToFile(String log) {
// 模拟写入文件操作
System.out.println("写入文件[" + filePath + "]: " + log);
}
private String getTimestamp() {
return java.time.LocalDateTime.now().toString();
}
}
/**
* 控制台日志记录器 - 具体产品角色
* 将日志输出到控制台
*/
public class ConsoleLogger implements Logger {
@Override
public void info(String message) {
System.out.println("\u001B[32m[INFO] " + getTimestamp() + " " + message + "\u001B[0m");
}
@Override
public void error(String message) {
System.out.println("\u001B[31m[ERROR] " + getTimestamp() + " " + message + "\u001B[0m");
}
@Override
public void warn(String message) {
System.out.println("\u001B[33m[WARN] " + getTimestamp() + " " + message + "\u001B[0m");
}
@Override
public void debug(String message) {
System.out.println("\u001B[36m[DEBUG] " + getTimestamp() + " " + message + "\u001B[0m");
}
private String getTimestamp() {
return java.time.LocalDateTime.now().toString();
}
}
/**
* 数据库日志记录器 - 具体产品角色
* 将日志记录到数据库中
*/
public class DatabaseLogger implements Logger {
private String dataSource;
public DatabaseLogger(String dataSource) {
this.dataSource = dataSource;
System.out.println("初始化数据库日志记录器,数据源: " + dataSource);
}
@Override
public void info(String message) {
String log = String.format("INSERT INTO logs (level, message, timestamp) VALUES ('INFO', '%s', '%s')",
message, getTimestamp());
executeSQL(log);
}
@Override
public void error(String message) {
String log = String.format("INSERT INTO logs (level, message, timestamp) VALUES ('ERROR', '%s', '%s')",
message, getTimestamp());
executeSQL(log);
}
@Override
public void warn(String message) {
String log = String.format("INSERT INTO logs (level, message, timestamp) VALUES ('WARN', '%s', '%s')",
message, getTimestamp());
executeSQL(log);
}
@Override
public void debug(String message) {
String log = String.format("INSERT INTO logs (level, message, timestamp) VALUES ('DEBUG', '%s', '%s')",
message, getTimestamp());
executeSQL(log);
}
private void executeSQL(String sql) {
// 模拟执行SQL
System.out.println("执行SQL: " + sql);
}
private String getTimestamp() {
return java.time.LocalDateTime.now().toString();
}
}
3. 定义抽象工厂
java
/**
* 日志记录器工厂接口 - 抽象工厂角色
* 声明工厂方法,由子类实现具体创建逻辑
*/
public interface LoggerFactory {
/**
* 工厂方法 - 创建日志记录器
* @return 日志记录器实例
*/
Logger createLogger();
/**
* 使用日志记录器记录信息
* @param message 日志信息
*/
default void logInfo(String message) {
Logger logger = createLogger();
logger.info(message);
}
/**
* 使用日志记录器记录错误
* @param message 错误信息
*/
default void logError(String message) {
Logger logger = createLogger();
logger.error(message);
}
}
4. 实现具体工厂
java
/**
* 文件日志记录器工厂 - 具体工厂角色
* 负责创建文件日志记录器
*/
public class FileLoggerFactory implements LoggerFactory {
private String filePath;
public FileLoggerFactory(String filePath) {
this.filePath = filePath;
}
@Override
public Logger createLogger() {
return new FileLogger(filePath);
}
}
/**
* 控制台日志记录器工厂 - 具体工厂角色
* 负责创建控制台日志记录器
*/
public class ConsoleLoggerFactory implements LoggerFactory {
@Override
public Logger createLogger() {
return new ConsoleLogger();
}
}
/**
* 数据库日志记录器工厂 - 具体工厂角色
* 负责创建数据库日志记录器
*/
public class DatabaseLoggerFactory implements LoggerFactory {
private String dataSource;
public DatabaseLoggerFactory(String dataSource) {
this.dataSource = dataSource;
}
@Override
public Logger createLogger() {
return new DatabaseLogger(dataSource);
}
}
5. 客户端使用示例
java
/**
* 应用程序类 - 客户端代码
* 演示工厂方法模式的使用
*/
public class Application {
public static void main(String[] args) {
System.out.println("=== 工厂方法模式演示 ===\n");
// 使用控制台日志
System.out.println("1. 使用控制台日志记录器:");
useConsoleLogger();
// 使用文件日志
System.out.println("\n2. 使用文件日志记录器:");
useFileLogger();
// 使用数据库日志
System.out.println("\n3. 使用数据库日志记录器:");
useDatabaseLogger();
// 演示多态性
System.out.println("\n4. 演示多态性:");
demonstratePolymorphism();
}
private static void useConsoleLogger() {
LoggerFactory factory = new ConsoleLoggerFactory();
Logger logger = factory.createLogger();
logger.info("应用程序启动成功");
logger.warn("内存使用率较高");
logger.error("数据库连接失败");
logger.debug("调试信息: 变量值 = 100");
}
private static void useFileLogger() {
LoggerFactory factory = new FileLoggerFactory("/var/log/myapp.log");
Logger logger = factory.createLogger();
logger.info("用户登录成功");
logger.error("文件上传失败");
// 使用默认方法
factory.logInfo("通过工厂默认方法记录日志");
}
private static void useDatabaseLogger() {
LoggerFactory factory = new DatabaseLoggerFactory("jdbc:mysql://localhost:3306/logs");
Logger logger = factory.createLogger();
logger.info("订单创建成功");
logger.error("支付处理异常");
}
private static void demonstratePolymorphism() {
// 多态性的体现:可以轻松切换不同的日志实现
LoggerFactory[] factories = {
new ConsoleLoggerFactory(),
new FileLoggerFactory("/tmp/app.log"),
new DatabaseLoggerFactory("jdbc:mysql://localhost:3306/app")
};
for (LoggerFactory factory : factories) {
System.out.println("\n使用 " + factory.getClass().getSimpleName() + ":");
factory.logInfo("这是一条测试日志信息");
factory.logError("这是一个测试错误信息");
}
}
}
6. 进阶示例:配置化的日志工厂
java
/**
* 配置化日志工厂 - 根据配置创建不同的日志记录器
*/
public class ConfigurableLoggerFactory implements LoggerFactory {
private LoggerConfig config;
public ConfigurableLoggerFactory(LoggerConfig config) {
this.config = config;
}
@Override
public Logger createLogger() {
switch (config.getType()) {
case CONSOLE:
return new ConsoleLogger();
case FILE:
return new FileLogger(config.getFilePath());
case DATABASE:
return new DatabaseLogger(config.getDataSource());
default:
throw new IllegalArgumentException("不支持的日志类型: " + config.getType());
}
}
}
/**
* 日志配置类
*/
public class LoggerConfig {
private LoggerType type;
private String filePath;
private String dataSource;
public LoggerConfig(LoggerType type, String filePath, String dataSource) {
this.type = type;
this.filePath = filePath;
this.dataSource = dataSource;
}
// Getter 方法
public LoggerType getType() { return type; }
public String getFilePath() { return filePath; }
public String getDataSource() { return dataSource; }
}
/**
* 日志类型枚举
*/
public enum LoggerType {
CONSOLE, FILE, DATABASE
}
工厂方法模式的优点
1. 符合开闭原则
java
// 添加新的日志类型时,不需要修改现有代码
public class CloudLogger implements Logger {
// 实现云日志记录...
}
public class CloudLoggerFactory implements LoggerFactory {
@Override
public Logger createLogger() {
return new CloudLogger(); // 新增工厂,不修改现有代码
}
}
2. 客户端与具体产品解耦
java
public class BusinessService {
private LoggerFactory loggerFactory;
public BusinessService(LoggerFactory loggerFactory) {
this.loggerFactory = loggerFactory; // 依赖抽象,不依赖具体实现
}
public void processBusiness() {
Logger logger = loggerFactory.createLogger();
logger.info("业务处理开始");
// 业务逻辑...
logger.info("业务处理完成");
}
}
3. 提高代码的可测试性
java
// 测试时可以使用Mock工厂
public class TestLoggerFactory implements LoggerFactory {
@Override
public Logger createLogger() {
return new MockLogger(); // 返回测试用的Mock对象
}
}
工厂方法模式的缺点
1. 类的数量增加
每个具体产品都需要对应一个具体工厂类,会导致系统中类的数量成对增加。
2. 增加了系统的复杂性
对于简单对象的创建,使用工厂方法模式可能会显得过于复杂。
适用场景
- 无法预知对象的确切类型:运行时才能确定要创建的对象
- 希望扩展产品类型:需要添加新产品时不影响现有代码
- 需要解耦客户端和具体产品:客户端只关心产品接口
- 需要为不同的上下文提供不同的产品实现
与简单工厂模式的对比
特性 | 简单工厂模式 | 工厂方法模式 |
---|---|---|
创建逻辑 | 集中在单个工厂类 | 分散在多个具体工厂类 |
开闭原则 | 违反(修改需要改动工厂类) | 符合(扩展新工厂即可) |
复杂度 | 相对简单 | 相对复杂 |
灵活性 | 较低 | 较高 |
适用场景 | 产品类型固定,变化较少 | 产品类型可能扩展 |
最佳实践
1. 使用依赖注入
java
public class ApplicationContext {
private static Map<String, LoggerFactory> factories = new HashMap<>();
static {
// 注册所有工厂
factories.put("console", new ConsoleLoggerFactory());
factories.put("file", new FileLoggerFactory("/app.log"));
}
public static LoggerFactory getLoggerFactory(String type) {
return factories.get(type);
}
}
2. 结合配置文件
java
// 通过配置文件决定使用哪个工厂
Properties config = loadConfig();
String factoryType = config.getProperty("logger.factory");
LoggerFactory factory = ApplicationContext.getLoggerFactory(factoryType);
3. 使用泛型增强类型安全
java
public interface LoggerFactory<T extends Logger> {
T createLogger();
}
public class FileLoggerFactory implements LoggerFactory<FileLogger> {
@Override
public FileLogger createLogger() {
return new FileLogger("/app.log");
}
}
总结
工厂方法模式通过将对象的创建延迟到子类,完美解决了简单工厂模式违反开闭原则的问题。它提供了一种灵活的扩展机制,使得系统能够轻松应对变化。
核心价值:
- 真正实现了面向对象设计的"开闭原则"
- 提供了优秀的扩展性和维护性
- 实现了创建逻辑与使用逻辑的彻底分离
- 为框架设计和组件化提供了坚实基础
掌握工厂方法模式,能够帮助我们在面对复杂对象创建场景时,设计出更加灵活、可维护的系统架构。