工厂方法模式 (Factory Method) —— 各自为政的作坊
前言
在深入探讨工厂方法模式之前,想向大家推荐一个非常棒的开源项目:design-patterns-23。这个项目用最现代的技术栈重写了 23 种设计模式,非常适合实战学习,还配套了在线交互演示站,可以边读文章边动手玩。本文的实战案例灵感也来源于此。
1. 工厂方法模式是什么?
工厂方法模式(Factory Method Pattern)定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
通俗解释:
- 简单工厂(Simple Factory):就像一个大杂烩工厂。你想买鞋,阿迪、耐克都在这一个工厂里造。如果想造新百伦,还得改这个大工厂的流水线。
- 工厂方法(Factory Method):就像品牌专卖店。阿迪有专门的阿迪工厂,耐克有专门的耐克工厂。你想买阿迪,就去阿迪工厂;想买新百伦?没问题,开一家新百伦工厂就行,完全不用去动阿迪的工厂。
1.1 为什么我们需要它?
核心痛点:违反开闭原则 (Open/Closed Principle)。 在简单工厂模式中,工厂类集中了所有产品的创建逻辑。一旦要增加新产品,就必须修改工厂类的 if-else 或 switch-case 代码。
烂代码示例(简单工厂):
public class SimpleFactory {
public static Product createProduct(String type) {
if ("A".equals(type)) {
return new ProductA();
} else if ("B".equals(type)) {
return new ProductB();
} else {
return null;
}
}
}如果我要加个 ProductC,我必须去改 SimpleFactory 的代码。这在大型项目中是危险的,因为修改已有代码可能会破坏原有的稳定性。
2. Java 实战演练:日志记录器系统
假设我们要开发一个日志记录器,支持记录到文件(FileLogger)和记录到数据库(DatabaseLogger)。未来可能还会支持记录到云端。
2.1 定义产品接口 (Logger)
这是所有日志记录器的共同规范。
public interface Logger {
void writeLog();
}2.2 具体产品实现
文件日志记录器:
public class FileLogger implements Logger {
@Override
public void writeLog() {
System.out.println("把日志写入文件...");
}
}数据库日志记录器:
public class DatabaseLogger implements Logger {
@Override
public void writeLog() {
System.out.println("把日志写入数据库...");
}
}2.3 定义工厂接口 (LoggerFactory)
注意,这里不再是一个具体的类,而是一个接口。它只规定了“工厂应该能生产日志记录器”,具体怎么生产,由子类决定。
public interface LoggerFactory {
Logger createLogger();
}2.4 具体工厂实现
文件日志工厂:只负责生产文件日志记录器。
public class FileLoggerFactory implements LoggerFactory {
@Override
public Logger createLogger() {
// 这里可以包含复杂的初始化逻辑,比如打开文件流等
return new FileLogger();
}
}数据库日志工厂:只负责生产数据库日志记录器。
public class DatabaseLoggerFactory implements LoggerFactory {
@Override
public Logger createLogger() {
// 这里可以包含连接数据库的逻辑
return new DatabaseLogger();
}
}2.5 客户端调用
客户端不再依赖具体的 FileLogger,而是依赖抽象的 LoggerFactory。
public class Client {
public static void main(String[] args) {
// 1. 想要文件日志,就找文件工厂
LoggerFactory factory = new FileLoggerFactory();
Logger logger = factory.createLogger();
logger.writeLog();
// 2. 想要数据库日志,就换成数据库工厂
// 注意:这里只需要改这一行代码,后续逻辑都不用动
factory = new DatabaseLoggerFactory();
logger = factory.createLogger();
logger.writeLog();
}
}3. 进阶思考:开闭原则的胜利
现在,老板说要加一个“云端日志记录器”(CloudLogger)。我们需要改现有代码吗? 完全不需要! 我们只需要:
- 新建
CloudLogger实现Logger接口。 - 新建
CloudLoggerFactory实现LoggerFactory接口。
老的 FileLogger、FileLoggerFactory 代码连碰都不用碰。这就完美符合了开闭原则:对扩展开放,对修改关闭。
3.1 简单工厂 vs 工厂方法 vs 静态工厂方法
这也是面试中容易混淆的概念。
简单工厂 (Simple Factory):
- 不是 GoF 23 种设计模式之一,更像是一种编程习惯。
- 优点:简单,不需要定义一堆工厂类。
- 缺点:违背开闭原则,扩展困难。
工厂方法 (Factory Method):
- GoF 23 种标准模式之一。
- 优点:符合开闭原则,扩展性好。
- 缺点:代码量增加。
静态工厂方法 (Static Factory Method):
- 这指的是
Integer.valueOf("123")这种写法。 - 它不是一种设计模式,而是一种代码惯例(Idiom),通常用来替代构造函数。
- 优点:
- 有名字(构造函数必须和类名一样,无法表达含义)。
- 不需要每次都创建新对象(比如
Boolean.valueOf(true)返回的是缓存对象)。 - 可以返回子类型。
- 这指的是
3.2 依赖注入 (Dependency Injection) 的降维打击
在现代 Spring 开发中,你可能会发现我们很少手写工厂类了。为什么? 因为 Spring 容器本身就是一个超级工厂。
当我们在 XML 或注解中配置 <bean id="myService" class="com.example.MyServiceImpl"/> 时,Spring 容器负责了创建对象、装配依赖的过程。
@Autowired:相当于调用了工厂获取产品。@Component/@Bean:相当于注册了产品及其创建逻辑。
Spring 的 DI (Dependency Injection) 机制,在很大程度上取代了手写工厂方法模式的需求,让开发者专注于业务逻辑。
4. 源码中的工厂方法模式
JDK 和 Spring 中到处都是工厂方法模式的影子:
java.util.Collection接口:iterator()方法就是一个工厂方法。javapublic interface Collection<E> extends Iterable<E> { // 工厂方法,要求子类必须返回一个 Iterator 对象 Iterator<E> iterator(); }ArrayList返回Itr,HashSet返回KeyIterator。客户端只知道返回的是Iterator,不关心具体是谁。java.net.URLStreamHandlerFactory: 用于根据协议(http, ftp)创建 URLStreamHandler。Spring 的
FactoryBean接口: 这是 Spring 框架的核心接口之一。javapublic interface FactoryBean<T> { T getObject() throws Exception; Class<?> getObjectType(); boolean isSingleton(); }当你实现这个接口并注册到 Spring 容器时,Spring 就会调用
getObject()来获取你真正想要生成的 Bean。这完全就是工厂方法模式的应用。典型的例子是SqlSessionFactoryBean(MyBatis) 或LocalSessionFactoryBean(Hibernate)。SLF4J 的
ILoggerFactory: SLF4J 只是一个门面(Facade),它通过ILoggerFactory接口来适配不同的日志实现(Logback, Log4j)。javapublic interface ILoggerFactory { Logger getLogger(String name); }Logback 的
LoggerContext实现了这个接口,负责创建 Logback 的 Logger 对象。
5. 优缺点与适用场景总结
优点
- 解耦:客户端只需要知道具体工厂的名称,不需要知道具体产品的类名,更不需要知道产品是如何创建的。
- 符合开闭原则:增加新产品时,只需要增加相应的具体产品类和工厂类,无需修改现有代码。
- 屏蔽复杂性:如果产品的创建过程非常复杂(比如需要读配置、建立连接),工厂方法可以将这些逻辑封装在工厂内部,客户端只需调用
create()即可。
缺点
- 类爆炸:每增加一种产品,就需要增加一个具体产品类和一个具体工厂类。如果有 100 种产品,就得写 200 个类,增加了系统的复杂度。
- 抽象层增加了理解难度:引入了抽象层,对于简单的对象创建,反而显得繁琐。
6. 实验实操
在 design-patterns-web 项目中,左侧展示了非工厂模式的紧耦合做法,你需要分别点击 new Car(), new Truck(), new Bike() 来创建对象,客户端必须知道具体的类名。 右侧展示了工厂方法模式,你只需要选择产品类型(Car/Truck/Bike),然后点击“Order from Factory”。工厂会负责创建具体对象,客户端无需关心实例化的细节,实现了创建者与具体产品类的解耦。 截图建议:截取左右两侧对比,左侧显示手动创建的多种对象,右侧显示通过工厂统一创建的对象。
适用场景
- 不知道具体类名:一个类不知道它所需要的对象的类:客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可。
- 扩展性要求高:需要提供给第三方库或框架使用,允许使用者扩展具体实现。
- 初始化逻辑复杂:对象的创建包含复杂的逻辑(如依赖注入、配置读取),不适合直接
new。
6. 结语
工厂方法模式是多态在对象创建过程中的极致体现。它通过将实例化的责任推迟到子类,解决了简单工厂模式无法灵活扩展的弊端。虽然它带来了类数量的增加,但在大型系统中,这种“用空间换灵活性”的交易通常是值得的。
在面试中,能清晰地区分简单工厂、工厂方法和抽象工厂(下文会讲),并能结合 Spring FactoryBean 谈出深度,是加分项。