一、从简单工厂说起
讲工厂方法模式之前,我得先聊聊简单工厂。说实话,简单工厂并不是GoF 23种设计模式之一,但它却是理解工厂方法最好的敲门砖。
简单工厂长什么样?说白了就是:一个工厂类,根据参数决定创建哪个产品。我刚开始写Java那会儿,特别喜欢这么干——把所有创建逻辑塞到一个类里,调用方传个字符串或者枚举,工厂就给你返回对应的对象。
举个例子,假设我们要做一个日志记录器:
// 产品接口
public interface Logger {
void log(String message);
}
// 具体产品
public class FileLogger implements Logger {
public void log(String message) {
System.out.println("写入文件: " + message);
}
}
public class ConsoleLogger implements Logger {
public void log(String message) {
System.out.println("控制台输出: " + message);
}
}
// 简单工厂
public class LoggerFactory {
public static Logger createLogger(String type) {
if ("file".equals(type)) {
return new FileLogger();
} else if ("console".equals(type)) {
return new ConsoleLogger();
}
throw new IllegalArgumentException("未知的日志类型");
}
}
嗯,这段代码看起来挺清爽的,对吧?但我在项目中遇到过一个问题:每次新增一种日志类型,都得改LoggerFactory的代码。这违反了开闭原则——对扩展开放,对修改关闭。
⚠️ 简单工厂的致命伤:当产品种类增多,工厂类会变得臃肿不堪。而且每次新增产品都要修改工厂类,容易引发回归问题。
二、工厂方法模式的定义
那怎么解决简单工厂的痛点呢?工厂方法模式给出了答案。
工厂方法模式的定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
你想想看,这句话的核心是什么?把「创建对象」这个动作,从父类下放到子类。父类只定义「怎么用」,子类决定「用哪个」。
🎯 核心思想:不再用一个万能工厂包揽所有创建逻辑,而是为每一种产品配备一个专门的工厂。每个工厂只负责生产一种产品。
三、模式结构
工厂方法模式的结构其实很清晰,我画了一张图帮你理解:
结构中的角色就四个:
- 抽象工厂(Factory):声明工厂方法,返回一个产品对象
- 具体工厂(ConcreteFactory):实现工厂方法,创建具体的产品
- 抽象产品(Product):定义产品的接口
- 具体产品(ConcreteProduct):实现产品接口的具体类
四、代码实现
还是用日志记录器的例子,我们用工厂方法模式重写一遍:
// 1. 抽象产品
public interface Logger {
void log(String message);
}
// 2. 具体产品
public class FileLogger implements Logger {
public void log(String message) {
System.out.println("写入文件: " + message);
}
}
public class ConsoleLogger implements Logger {
public void log(String message) {
System.out.println("控制台输出: " + message);
}
}
// 3. 抽象工厂
public abstract class LoggerFactory {
public abstract Logger createLogger();
// 这里可以加一些公共逻辑
public void logMessage(String message) {
Logger logger = createLogger();
logger.log(message);
}
}
// 4. 具体工厂
public class FileLoggerFactory extends LoggerFactory {
public Logger createLogger() {
return new FileLogger();
}
}
public class ConsoleLoggerFactory extends LoggerFactory {
public Logger createLogger() {
return new ConsoleLogger();
}
}
// 5. 客户端使用
public class Client {
public static void main(String[] args) {
LoggerFactory factory = new FileLoggerFactory();
factory.logMessage("这是一条文件日志");
factory = new ConsoleLoggerFactory();
factory.logMessage("这是一条控制台日志");
}
}
💡 我的习惯:在抽象工厂里,我通常会加一个模板方法(比如上面的logMessage),把「创建+使用」的逻辑封装在一起。这样客户端只需要关心用哪个工厂,不用管创建和调用的细节。
五、优缺点分析
| ✅ 优点 | ❌ 缺点 |
|---|---|
|
|
⚠️ 我曾经踩过的坑:有一次在一个小工具里用了工厂方法模式,结果就两种产品,硬是写了6个类。后来同事接手时吐槽说「这代码过度设计了」。所以我的建议是:产品种类少于3个时,用简单工厂就够了;超过3个且可能继续扩展时,再考虑工厂方法。
六、什么时候用工厂方法?
我个人总结了三个适用场景:
- 客户端不知道它需要创建哪个具体类——比如框架代码,由子类决定实例化哪个类
- 一个类希望由它的子类来指定创建的对象——父类定义行为框架,子类填充具体实现
- 将创建逻辑从使用逻辑中分离出来——说白了就是解耦,让创建和使用各司其职
你想想看,其实很多框架都在用工厂方法模式。比如Spring的BeanFactory,虽然它用的是更高级的抽象,但底层思想是一样的——把「创建对象」这件事抽象出来,交给专门的组件去处理。
📌 一句话总结:工厂方法模式,就是用继承的方式,把「创建对象」这个变化点封装起来。当你的系统需要支持多种产品,且产品种类可能不断扩展时,它就是你的最佳选择。
公众号:蓝海资料掘金营,微信deep3321