工厂方法模式 (Factory Method Pattern)
什么是工厂方法模式?
工厂方法模式是一种创建型设计模式,它定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
简单来说:工厂方法模式就是用一个"工厂"类来创建对象,而不是直接使用new关键字。
生活中的例子
想象一家汽车制造厂:
- 抽象产品:汽车(所有汽车都有共同的特征)
- 具体产品:奔驰、宝马、奥迪等不同品牌的汽车
- 抽象工厂:汽车工厂(定义了制造汽车的方法)
- 具体工厂:奔驰工厂、宝马工厂、奥迪工厂(分别制造对应品牌的汽车)
为什么需要工厂方法模式?
传统方式的问题
java
// 直接使用new创建对象
Car car = new Benz();
问题:
- 客户端与具体类耦合:如果需要换成宝马,必须修改代码
- 创建逻辑分散:对象的创建逻辑散落在代码各处
- 扩展困难:新增车型时需要修改多处代码
工厂方法模式的优势
java
// 使用工厂创建对象
Car car = carFactory.createCar();
优势:
- 解耦:客户端不需要知道具体创建哪个对象
- 统一管理:对象的创建逻辑集中在工厂类中
- 易于扩展:新增产品时,只需新增对应的工厂类
工厂方法模式的结构
┌─────────────────────┐
│ Product (产品) │ 抽象产品
├─────────────────────┤
│ + operation(): void │
└──────────┬──────────┘
│ 继承
├──┬──────────────────┬──────────────┐
│ │ │
┌──────────┴──────┐ ┌───────────┴───────┐ ┌───┴────────┐
│ ConcreteProduct1 │ │ ConcreteProduct2 │ │ ... │ 具体产品
└─────────────────┘ └───────────────────┘ └────────────┘
┌─────────────────────┐
│ Creator (工厂) │ 抽象工厂
├─────────────────────┤
│ + factoryMethod(): │
│ Product │
│ + anOperation(): │
│ void │
└──────────┬──────────┘
│ 继承
├──┬──────────────────┬──────────────┐
│ │ │
┌──────────┴──────┐ ┌───────────┴───────┐ ┌───┴────────┐
│ ConcreteCreator1 │ │ ConcreteCreator2 │ │ ... │ 具体工厂
└─────────────────┘ └───────────────────┘ └────────────┘
代码示例
1. 定义抽象产品
java
/**
* 抽象产品:日志记录器
*/
public interface Logger {
/**
* 记录日志
*/
void log(String message);
/**
* 记录错误
*/
void error(String message);
}
2. 定义具体产品
java
/**
* 具体产品:控制台日志记录器
*/
public class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println("[INFO] " + message);
}
@Override
public void error(String message) {
System.out.println("[ERROR] " + message);
}
}
/**
* 具体产品:文件日志记录器
*/
public class FileLogger implements Logger {
@Override
public void log(String message) {
System.out.println("[FILE LOG] " + message);
}
@Override
public void error(String message) {
System.out.println("[FILE ERROR] " + message);
}
}
/**
* 具体产品:数据库日志记录器
*/
public class DatabaseLogger implements Logger {
@Override
public void log(String message) {
System.out.println("[DB LOG] " + message);
}
@Override
public void error(String message) {
System.out.println("[DB ERROR] " + message);
}
}
3. 定义抽象工厂
java
/**
* 抽象工厂:日志记录器工厂
*/
public abstract class LoggerFactory {
/**
* 工厂方法:创建日志记录器
*/
public abstract Logger createLogger();
/**
* 模板方法:记录日志消息
*/
public void logMessage(String message) {
Logger logger = createLogger();
logger.log(message);
}
/**
* 模板方法:记录错误消息
*/
public void logError(String message) {
Logger logger = createLogger();
logger.error(message);
}
}
4. 定义具体工厂
java
/**
* 具体工厂:控制台日志记录器工厂
*/
public class ConsoleLoggerFactory extends LoggerFactory {
@Override
public Logger createLogger() {
return new ConsoleLogger();
}
}
/**
* 具体工厂:文件日志记录器工厂
*/
public class FileLoggerFactory extends LoggerFactory {
@Override
public Logger createLogger() {
return new FileLogger();
}
}
/**
* 具体工厂:数据库日志记录器工厂
*/
public class DatabaseLoggerFactory extends LoggerFactory {
@Override
public Logger createLogger() {
return new DatabaseLogger();
}
}
5. 使用工厂
java
/**
* 工厂方法模式测试类
* 演示如何使用工厂方法模式创建不同类型的日志记录器
*/
public class FactoryMethodTest {
public static void main(String[] args) {
System.out.println("=== 工厂方法模式测试 ===\n");
// 测试控制台日志记录器
System.out.println("--- 测试控制台日志记录器 ---");
LoggerFactory consoleFactory = new ConsoleLoggerFactory();
Logger consoleLogger = consoleFactory.createLogger();
consoleLogger.log("应用程序启动");
consoleLogger.error("发生了一个错误");
System.out.println();
// 测试文件日志记录器
System.out.println("--- 测试文件日志记录器 ---");
LoggerFactory fileFactory = new FileLoggerFactory();
Logger fileLogger = fileFactory.createLogger();
fileLogger.log("用户登录成功");
fileLogger.error("数据库连接失败");
System.out.println();
// 测试数据库日志记录器
System.out.println("--- 测试数据库日志记录器 ---");
LoggerFactory dbFactory = new DatabaseLoggerFactory();
Logger dbLogger = dbFactory.createLogger();
dbLogger.log("订单创建成功");
dbLogger.error("支付网关超时");
System.out.println("\n=== 使用模板方法 ===");
// 使用模板方法
consoleFactory.logMessage("使用模板方法记录日志");
consoleFactory.logError("使用模板方法记录错误");
System.out.println();
fileFactory.logMessage("使用模板方法记录日志到文件");
fileFactory.logError("使用模板方法记录错误到文件");
System.out.println("\n=== 扩展性测试 ===");
System.out.println("如果需要新增一种日志记录器(如:网络日志),");
System.out.println("只需:");
System.out.println("1. 创建 NetworkLogger 实现 Logger 接口");
System.out.println("2. 创建 NetworkLoggerFactory 继承 LoggerFactory");
System.out.println("3. 无需修改任何现有代码,符合开闭原则!");
}
}
工厂方法模式的优点
- 符合开闭原则:新增产品时,只需新增对应的工厂类,无需修改现有代码
- 代码解耦:客户端与具体产品解耦,只依赖抽象产品
- 职责单一:每个具体工厂只负责创建一种产品
- 易于测试:可以轻松替换具体工厂进行测试
工厂方法模式的缺点
- 类数量增加:每增加一个产品,就需要增加一个具体工厂类
- 系统复杂度增加:引入了抽象层,增加了系统的理解难度
- 简单场景过度设计:如果产品种类很少,使用工厂方法模式可能显得过于复杂
适用场景
- 不知道具体要创建哪个产品:客户端只知道需要产品,但不知道具体是哪个
- 希望将创建逻辑延迟到子类:让子类决定创建哪个具体产品
- 产品种类较多:有多种产品需要创建,且可能继续增加
- 需要解耦客户端和具体产品:客户端不依赖具体产品类
常见应用场景
- 日志框架:不同类型的日志记录器(文件日志、控制台日志、数据库日志)
- 数据库连接:不同类型的数据库连接(MySQL、Oracle、PostgreSQL)
- 支付系统:不同的支付方式(支付宝、微信、银行卡)
- 消息队列:不同的消息队列实现(RabbitMQ、Kafka、ActiveMQ)
与简单工厂的区别
| 特性 | 简单工厂 | 工厂方法 |
|---|---|---|
| 工厂类数量 | 1个工厂类 | 多个工厂类 |
| 创建逻辑 | 集中在一个工厂类 | 分散在各个具体工厂类 |
| 扩展性 | 修改工厂类 | 新增工厂类 |
| 符合开闭原则 | ❌ | ✅ |
| 复杂度 | 简单 | 较复杂 |
使用建议
- 产品种类少且固定:使用简单工厂
- 产品种类多且可能增加:使用工厂方法模式
- 需要遵循开闭原则:使用工厂方法模式
- 创建逻辑复杂:使用工厂方法模式,将创建逻辑分散到各个工厂类中
注意事项
⚠️ 工厂方法模式虽然很好,但不要滥用:
- 如果产品种类很少(1-2个),直接使用
new可能更简单 - 如果创建逻辑很简单,可以考虑使用简单工厂
- 工厂方法模式会增加类的数量,要权衡利弊