概述
在面向对象设计的广阔天地中,对象创建是一个永恒的话题。工厂方法模式(Factory Method Pattern)作为 GoF 23 种经典设计模式之一,为我们提供了一种优雅的对象创建范式。它的核心意图清晰而深刻:定义一个用于创建对象的接口,让子类决定实例化哪一个类。这种设计将对象的实例化过程延迟到子类中完成,从而在客户端代码与具体产品类之间建立起一道解耦的屏障。
工厂方法模式所解决的核心问题是将对象创建逻辑与业务逻辑解耦 。试想,当我们的客户端代码充斥着大量 new 操作符时,系统将变得僵硬而脆弱------每新增一种产品类型,都需要修改客户端代码,这无疑违背了开闭原则。工厂方法模式通过引入抽象工厂角色,使得客户端仅依赖于抽象接口,具体产品的创建由子类工厂负责,从而实现了"新增产品时不修改已有代码"的优雅扩展。
本文将从四个维度系统性地剖析工厂方法模式:首先,我们将从原始代码的耦合困境出发,经历简单工厂的便利与局限,最终演进到工厂方法模式的正确实践;其次,深入 JDK、Spring、SLF4J 等经典框架源码,揭示该模式在工业级代码中的精妙应用;再次,通过多维对比辨析,厘清工厂方法与简单工厂、抽象工厂、模板方法等易混淆概念的本质差异;最后,结合面试高频问题给出专家级解答。让我们一同踏上这段从模式本质到工程实践的深度旅程。
一、模式定义与结构
1.1 GoF 标准定义
工厂方法模式的经典定义出自《设计模式:可复用面向对象软件的基础》:
Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
这一定义揭示了工厂方法模式的两个关键特征:一是存在一个抽象的创建接口(工厂方法),二是具体创建逻辑由子类实现。这种"延迟实例化"的机制为系统带来了极大的灵活性。
1.2 UML 类图(Mermaid Flowchart)
以下使用 Mermaid 的 flowchart 语法绘制工厂方法模式的标准 UML 类图:
1.3 类图详细文字说明
上方的 flowchart 类图清晰地展示了工厂方法模式的四类核心角色及其相互关系。
抽象产品(Product) :位于图的左上角,是一个接口(或抽象类),定义了所有具体产品必须实现的操作契约。客户端仅依赖于此接口,从而避免了对具体产品类的直接引用。在图中,Product 声明了 operation() 方法,这是所有产品的公共行为。
具体产品(ConcreteProduct) :位于图的下方,ConcreteProductA 和 ConcreteProductB 实现了 Product 接口。每个具体产品都提供了 operation() 的具体实现。它们是工厂方法最终要创建的目标对象。注意具体产品与抽象产品之间的泛化关系(--|> 箭头)。
抽象工厂(Creator) :位于图的中央偏右,是一个抽象类。它声明了工厂方法 factoryMethod(),该方法返回一个 Product 类型的对象。同时,抽象工厂可能包含一些依赖产品对象的业务逻辑方法(如 someOperation()),这些方法会调用工厂方法来获取产品实例,然后执行后续操作。Creator 与 Product 之间的虚线依赖箭头(..>)表示工厂方法返回的是 Product 类型。
具体工厂(ConcreteCreator) :位于图的下方,ConcreteCreatorA 和 ConcreteCreatorB 继承自 Creator,并重写了工厂方法,分别返回 ConcreteProductA 和 ConcreteProductB 的实例。具体工厂与具体产品之间存在创建依赖关系(..> 虚线箭头标注 <<create>>),表示具体工厂负责实例化对应的具体产品。
数据流向与协作过程 :客户端通过依赖注入或直接调用获得一个 Creator 类型的引用(实际指向某个 ConcreteCreator 对象)。当客户端调用 someOperation() 时,该方法内部会调用 factoryMethod() 获取 Product 实例,然后调用 Product 的 operation() 方法。整个过程中,客户端代码只与 Creator 和 Product 这两个抽象角色交互,具体产品的创建被完全封装在子类工厂中。这种设计使得新增产品时只需增加对应的 ConcreteProduct 和 ConcreteCreator 类,而无需修改现有代码。
1.4 各角色职责与协作关系
| 角色 | 职责 | 协作关系 |
|---|---|---|
| Product | 定义产品的接口,是工厂方法所创建对象的抽象类型 | 被 Creator 依赖,被 ConcreteProduct 实现,被 Client 使用 |
| ConcreteProduct | 实现 Product 接口,提供具体产品的业务逻辑 | 由对应的 ConcreteCreator 创建 |
| Creator | 声明工厂方法(返回 Product 类型),并可提供默认实现;包含依赖于 Product 的业务逻辑 | 依赖 Product;被 Client 调用;子类实现工厂方法 |
| ConcreteCreator | 重写工厂方法,返回一个具体的 ConcreteProduct 实例 | 继承 Creator;创建并返回 ConcreteProduct |
协作的核心在于多态:Creator 的工厂方法是多态的,运行时根据实际子类类型来决定创建哪个具体产品。这种多态机制正是"让子类决定实例化哪一个类"的技术基础。
二、代码演进与实现
为了让读者深刻理解工厂方法模式的价值,我们将通过一个完整的代码演进过程来展示模式引入的动机与收益。以一个"日志记录器"的场景为例:系统需要支持文件日志和控制台日志两种记录方式。
2.1 原始代码:直接 new 的困境
java
// ========== 不使用设计模式的原始代码 ==========
// 具体产品类:文件日志记录器
class FileLogger {
public void log(String message) {
System.out.println("[FILE] Writing to file: " + message);
// 实际的文件写入逻辑...
}
}
// 具体产品类:控制台日志记录器
class ConsoleLogger {
public void log(String message) {
System.out.println("[CONSOLE] " + message);
}
}
// 客户端代码
public class OriginalClient {
public static void main(String[] args) {
String logType = "file"; // 假设从配置文件读取
// 问题1:客户端直接依赖具体产品类,导致紧耦合
// 问题2:新增日志类型(如数据库日志)时,必须修改客户端代码
// 问题3:创建逻辑分散在多处,难以统一管理和优化
if ("file".equals(logType)) {
FileLogger logger = new FileLogger(); // 直接 new
logger.log("This is a file log.");
} else if ("console".equals(logType)) {
ConsoleLogger logger = new ConsoleLogger(); // 直接 new
logger.log("This is a console log.");
}
}
}
耦合问题分析:
- 客户端代码直接依赖
FileLogger和ConsoleLogger具体类。 - 新增日志类型(如
DatabaseLogger)时,必须修改if-else分支,违反了开闭原则。 - 创建逻辑与业务逻辑混杂,代码复用性差。
2.2 简单工厂模式:便利性与局限性
简单工厂模式将创建逻辑集中到一个工厂类中,一定程度上缓解了客户端的创建压力。
java
// ========== 简单工厂模式 ==========
// 引入抽象产品接口(为后续扩展做准备)
interface Logger {
void log(String message);
}
class FileLogger implements Logger {
@Override
public void log(String message) {
System.out.println("[FILE] Writing to file: " + message);
}
}
class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println("[CONSOLE] " + message);
}
}
// 简单工厂类:负责所有产品的创建
class LoggerFactory {
public static Logger createLogger(String type) {
// 问题:每新增一种日志类型,都需要修改此处的 if-else,违背开闭原则
if ("file".equalsIgnoreCase(type)) {
return new FileLogger();
} else if ("console".equalsIgnoreCase(type)) {
return new ConsoleLogger();
} else {
throw new IllegalArgumentException("Unsupported logger type: " + type);
}
}
}
// 客户端代码
public class SimpleFactoryClient {
public static void main(String[] args) {
// 客户端不再直接 new 具体产品,而是通过工厂获取
Logger logger = LoggerFactory.createLogger("file");
logger.log("Simple Factory Pattern.");
// 便利性:客户端无需知道具体产品类名
// 局限性:工厂类集中了所有创建逻辑,扩展时需要修改工厂类代码
}
}
简单工厂的局限性:
- 违背开闭原则 :新增产品时必须修改工厂类的
createLogger方法(增加新的if-else分支)。 - 工厂类职责过重:包含所有产品的创建逻辑,随着产品增多,代码将变得臃肿且难以维护。
- 并非真正的设计模式:简单工厂只是一种编程习惯,GoF 并未将其列为一种设计模式。
2.3 工厂方法模式重构:拥抱开闭原则
现在,我们使用工厂方法模式进行重构,将对象的创建延迟到子类工厂中。
java
// ========== 工厂方法模式重构 ==========
// 1. 抽象产品接口(保持不变)
interface Logger {
void log(String message);
}
// 2. 具体产品类
class FileLogger implements Logger {
@Override
public void log(String message) {
System.out.println("[FILE] Writing to file: " + message);
}
}
class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println("[CONSOLE] " + message);
}
}
// 新增一种日志类型:数据库日志(用于演示开闭原则)
class DatabaseLogger implements Logger {
@Override
public void log(String message) {
System.out.println("[DATABASE] Saving log to DB: " + message);
}
}
// 3. 抽象工厂:声明工厂方法
abstract class LoggerCreator {
// 工厂方法:返回 Logger 类型的产品,由子类实现具体创建逻辑
public abstract Logger createLogger();
// 模板方法(可选):展示工厂方法如何与业务逻辑结合
public void writeLog(String message) {
// 调用工厂方法获取产品对象,此处体现了依赖倒置原则
Logger logger = createLogger();
// 使用产品对象执行业务操作
logger.log(message);
// 可以添加一些公共的前置或后置处理逻辑
System.out.println("[FACTORY METHOD] Log operation completed.");
}
}
// 4. 具体工厂类:每个具体工厂负责创建一种具体产品
class FileLoggerCreator extends LoggerCreator {
@Override
public Logger createLogger() {
// 子类决定实例化哪一个具体产品类
return new FileLogger();
}
}
class ConsoleLoggerCreator extends LoggerCreator {
@Override
public Logger createLogger() {
return new ConsoleLogger();
}
}
// 新增数据库日志工厂:扩展时只需新增类,无需修改已有代码
class DatabaseLoggerCreator extends LoggerCreator {
@Override
public Logger createLogger() {
return new DatabaseLogger();
}
}
// 5. 客户端代码:面向抽象编程
public class FactoryMethodClient {
public static void main(String[] args) {
// 客户端依赖于抽象工厂和抽象产品
LoggerCreator creator;
// 根据配置或运行时条件选择具体工厂(可结合简单工厂或依赖注入)
String config = "database"; // 模拟从配置文件读取
if ("file".equalsIgnoreCase(config)) {
creator = new FileLoggerCreator();
} else if ("console".equalsIgnoreCase(config)) {
creator = new ConsoleLoggerCreator();
} else if ("database".equalsIgnoreCase(config)) {
creator = new DatabaseLoggerCreator();
} else {
throw new IllegalArgumentException("Invalid config: " + config);
}
// 客户端只调用抽象方法,具体创建过程由子类完成
creator.writeLog("Factory Method Pattern in action!");
// 也可以直接获取 Logger 对象
Logger logger = creator.createLogger();
logger.log("Direct logger usage.");
}
}
工厂方法模式的优势验证:
- 开闭原则 :新增
DatabaseLogger时,我们只需新增DatabaseLogger和DatabaseLoggerCreator两个类,无需修改已有的FileLoggerCreator、ConsoleLoggerCreator等任何现有代码。 - 单一职责:每个具体工厂类只负责创建一种产品,职责清晰。
- 扩展性:未来增加新的日志类型时,扩展成本极低。
2.4 工厂方法模式的变体
在实际开发中,工厂方法模式有一些实用的变体形式,以适应不同场景的灵活性需求。
2.4.1 带参数的工厂方法
当工厂方法需要根据传入参数来决定创建哪个具体产品时,可以将参数传递给工厂方法。
java
// ========== 带参数的工厂方法 ==========
// 抽象工厂:工厂方法接收一个类型参数
abstract class ParameterizedLoggerCreator {
// 工厂方法接收参数,由子类根据参数决定创建哪个具体产品
public abstract Logger createLogger(String type);
public void writeLog(String type, String message) {
Logger logger = createLogger(type);
logger.log(message);
}
}
// 具体工厂:根据参数返回不同的产品
class FlexibleLoggerCreator extends ParameterizedLoggerCreator {
@Override
public Logger createLogger(String type) {
switch (type.toLowerCase()) {
case "file":
return new FileLogger();
case "console":
return new ConsoleLogger();
case "database":
return new DatabaseLogger();
default:
throw new IllegalArgumentException("Unknown type: " + type);
}
}
}
// 使用示例
class ParameterizedFactoryDemo {
public static void main(String[] args) {
ParameterizedLoggerCreator creator = new FlexibleLoggerCreator();
creator.writeLog("file", "This goes to file.");
creator.writeLog("console", "This goes to console.");
}
}
注意:这种变体实际上在一定程度上回到了简单工厂的模式,违背了开闭原则(新增产品时需修改 switch 分支)。它适用于产品类型相对固定、变化不频繁的场景,或作为工厂方法模式的辅助手段。
2.4.2 默认工厂方法的实现
抽象工厂可以为工厂方法提供一个默认实现,子类可以选择覆盖或继承该默认行为。
java
// ========== 带默认实现的工厂方法 ==========
abstract class DefaultLoggerCreator {
// 工厂方法提供默认实现(创建控制台日志)
public Logger createLogger() {
return new ConsoleLogger(); // 默认返回控制台日志记录器
}
public void writeLog(String message) {
Logger logger = createLogger();
logger.log(message);
}
}
// 子类可以选择覆盖默认实现
class FileLoggerCreatorWithDefault extends DefaultLoggerCreator {
@Override
public Logger createLogger() {
return new FileLogger(); // 覆盖为文件日志
}
}
// 另一个子类直接使用默认实现(控制台日志)
class ConsoleLoggerCreatorWithDefault extends DefaultLoggerCreator {
// 无需覆盖,直接继承默认工厂方法
}
// 演示
class DefaultFactoryMethodDemo {
public static void main(String[] args) {
DefaultLoggerCreator fileCreator = new FileLoggerCreatorWithDefault();
fileCreator.writeLog("This will be written to file.");
DefaultLoggerCreator consoleCreator = new ConsoleLoggerCreatorWithDefault();
consoleCreator.writeLog("This will be written to console.");
}
}
这种变体提供了便利性:当大部分子类期望相同的创建行为时,可减少重复代码。子类只在需要特殊行为时才覆盖工厂方法。
三、源码级应用分析
工厂方法模式在众多优秀的开源框架和 JDK 内部都有着广泛而深刻的运用。理解这些源码级实现,能够帮助我们将模式知识提升到工程实践的高度。
3.1 JDK 中的工厂方法模式案例
3.1.1 java.util.Calendar#getInstance()
Calendar 类是 Java 中处理日期和时间的经典抽象类,其 getInstance() 方法是一个典型的工厂方法应用。
java
// Calendar.java 源码片段(简化)
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
// 静态工厂方法,根据默认时区和区域返回合适的 Calendar 实现
public static Calendar getInstance() {
// 内部调用 createCalendar 方法,这是一个典型的工厂方法
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
}
// 私有静态方法,真正的创建逻辑(工厂方法)
private static Calendar createCalendar(TimeZone zone, Locale aLocale) {
// 根据 Locale 和配置返回不同的具体实现
// 例如:JapaneseImperialCalendar(日本历)、GregorianCalendar(公历)
CalendarProvider provider = LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
.getCalendarProvider();
if (provider != null) {
return provider.getInstance(zone, aLocale);
}
// 默认返回 GregorianCalendar
return new GregorianCalendar(zone, aLocale);
}
// 抽象方法,由子类实现
public abstract void add(int field, int amount);
// ...
}
模式分析:
- 抽象产品 :
Calendar抽象类。 - 具体产品 :
GregorianCalendar、JapaneseImperialCalendar等。 - 工厂方法 :
createCalendar方法,根据Locale和TimeZone参数决定返回哪个具体实现。 - 客户端代码 :调用
Calendar.getInstance()获取实例,无需关心具体是哪个日历系统。
3.1.2 java.text.NumberFormat#getInstance()
NumberFormat 是用于格式化数字的抽象类,其工厂方法链展示了工厂方法模式的嵌套使用。
java
// NumberFormat.java 源码片段(简化)
public abstract class NumberFormat extends Format {
// 工厂方法:返回默认区域设置的通用数字格式
public static final NumberFormat getInstance() {
return getInstance(Locale.getDefault(Locale.Category.FORMAT), NUMBERSTYLE);
}
// 带参数的工厂方法
public static NumberFormat getInstance(Locale inLocale) {
return getInstance(inLocale, NUMBERSTYLE);
}
// 核心工厂方法:根据区域和样式返回具体实现
private static NumberFormat getInstance(Locale desiredLocale, int choice) {
// 使用服务提供者机制加载具体实现
LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(NumberFormatProvider.class, desiredLocale);
NumberFormat numberFormat = getInstance(adapter, desiredLocale, choice);
if (numberFormat == null) {
numberFormat = new DecimalFormat(); // 默认实现
}
return numberFormat;
}
// 抽象方法,由子类实现具体的格式化逻辑
public abstract StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos);
}
// DecimalFormat 是具体产品类
public class DecimalFormat extends NumberFormat {
// 实现了抽象方法...
}
工厂方法链 :getInstance() → getInstance(Locale) → getInstance(Locale, int),层层调用,最终由私有工厂方法完成创建。
3.1.3 java.util.Iterator 与集合框架
集合框架中的 iterator() 方法是一个典型的工厂方法模式应用。每个具体集合类都实现了自己的迭代器。
java
// 抽象产品
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
// 抽象工厂(集合接口中声明了工厂方法)
public interface Collection<E> extends Iterable<E> {
// 工厂方法:返回 Iterator 产品
Iterator<E> iterator();
// ... 其他方法
}
// 具体工厂:ArrayList
public class ArrayList<E> extends AbstractList<E> implements List<E> {
// 实现工厂方法,返回具体迭代器产品 Itr
public Iterator<E> iterator() {
return new Itr();
}
// 具体产品:ArrayList 的内部迭代器类
private class Itr implements Iterator<E> {
int cursor; // 下一个元素的索引
int lastRet = -1; // 上一次返回元素的索引
public boolean hasNext() {
return cursor != size;
}
public E next() {
// ... 具体实现
}
}
}
// 具体工厂:LinkedList
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E> {
public Iterator<E> iterator() {
return new ListItr(0);
}
// 具体产品:LinkedList 的迭代器
private class ListItr implements ListIterator<E> {
// ... 双向链表的迭代逻辑
}
}
模式体现:
Collection接口声明了工厂方法iterator()。- 每个具体的集合类(
ArrayList、LinkedList、HashSet等)都实现了该方法,返回适合其内部数据结构的迭代器。 - 客户端代码通过
for (String s : list)或显式调用iterator()获取迭代器,完全不关心迭代器的具体类型。
3.1.4 java.net.URLStreamHandlerFactory
URLStreamHandlerFactory 是一个用于创建协议处理器的工厂接口,体现了工厂方法模式在 JDK 网络库中的应用。
java
// 抽象产品
public abstract class URLStreamHandler {
protected abstract URLConnection openConnection(URL u) throws IOException;
}
// 抽象工厂接口
public interface URLStreamHandlerFactory {
// 工厂方法:根据协议返回对应的 URLStreamHandler
URLStreamHandler createURLStreamHandler(String protocol);
}
// URL 类中使用工厂(简化)
public final class URL implements java.io.Serializable {
// 静态工厂成员
static URLStreamHandlerFactory factory;
public static void setURLStreamHandlerFactory(URLStreamHandlerFactory fac) {
// 安全检查和设置
factory = fac;
}
// 获取协议处理器的方法(内部调用工厂方法)
static URLStreamHandler getURLStreamHandler(String protocol) {
if (factory != null) {
// 使用设置的工厂创建处理器
URLStreamHandler handler = factory.createURLStreamHandler(protocol);
if (handler != null) return handler;
}
// 回退到默认处理(如 sun.net.www.protocol.http.Handler)
// ...
}
}
// 自定义工厂实现示例
class CustomURLHandlerFactory implements URLStreamHandlerFactory {
@Override
public URLStreamHandler createURLStreamHandler(String protocol) {
if ("custom".equals(protocol)) {
return new CustomURLStreamHandler();
}
return null;
}
}
3.2 Spring 框架深度剖析
Spring 框架中工厂方法模式的应用极为广泛且设计精巧,尤其是 FactoryBean 接口的设计思想值得深入探讨。
3.2.1 FactoryBean 接口的设计意图
FactoryBean 是 Spring 提供的一个用于定制 Bean 创建逻辑的工厂接口。它看似名为 FactoryBean,实则更准确地讲是工厂方法模式中抽象工厂角色的实现容器。
java
// org.springframework.beans.factory.FactoryBean 接口
public interface FactoryBean<T> {
// 工厂方法:返回由该工厂创建的 Bean 实例
@Nullable
T getObject() throws Exception;
// 返回创建的 Bean 的类型
@Nullable
Class<?> getObjectType();
// 是否是单例
default boolean isSingleton() {
return true;
}
}
设计意图剖析:
FactoryBean本身不是一个"创建 Bean 的工厂"(即它不是BeanFactory),而是一个可定制的 Bean 实例创建逻辑的载体。- 当某个 Bean 的创建过程非常复杂(如需要大量配置、依赖外部资源、涉及动态代理),通过 XML 或注解的常规方式难以表达时,可以实现
FactoryBean,将复杂的创建逻辑封装在getObject()方法中。 - Spring 容器在实例化
FactoryBean类型的 Bean 时,会识别并调用其getObject()方法,将返回的对象注册为最终的 Bean,而非FactoryBean本身。
工厂方法模式的体现:
FactoryBean<T>接口中的getObject()方法就是一个工厂方法。- 每个具体的
FactoryBean实现类都是一个具体工厂 ,负责创建特定类型的具体产品。 - 抽象产品由泛型
T表示。
3.2.2 BeanFactory 与 FactoryBean 的命名辨析
这是面试中的高频问题,二者名称相似但职责截然不同。
| 对比维度 | BeanFactory | FactoryBean |
|---|---|---|
| 设计模式角色 | IoC 容器(工厂方法模式的使用者,而非模式角色) | 工厂方法模式中的具体工厂 |
| 核心职责 | 管理 Bean 的生命周期,提供依赖注入、作用域管理等容器功能 | 封装特定 Bean 的复杂创建逻辑,产出最终交给容器管理的 Bean |
| 获取方式 | 容器本身,通过 applicationContext.getBean() 使用 |
作为普通 Bean 注册到容器,获取时返回其 getObject() 的产品 |
| 典型实现 | DefaultListableBeanFactory、XmlWebApplicationContext |
SqlSessionFactoryBean、ProxyFactoryBean |
| 创建对象方式 | 委托给 FactoryBean 或使用反射调用构造器 |
在 getObject() 方法中通过编码方式创建对象 |
总结 :BeanFactory 是 Spring IoC 容器的根接口,是一个管理 Bean 的工厂 ;而 FactoryBean 是创建特定复杂 Bean 的工厂 Bean ,它本身也由 BeanFactory 管理。命名中的"Factory"修饰对象不同,前者修饰容器本身,后者修饰 Bean 的职责。
3.2.3 AbstractApplicationContext 中的工厂方法
Spring 的 AbstractApplicationContext 抽象类中包含了多个工厂方法,用于创建容器内部组件。
java
// AbstractApplicationContext.java 部分源码(简化)
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
// 模板方法:定义刷新容器的骨架流程
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
// 工厂方法:获取 BeanFactory(由子类实现)
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
// ...
}
}
// 工厂方法:抽象方法,由具体子类实现
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
// 工厂方法:抽象方法,关闭 BeanFactory
protected abstract void closeBeanFactory();
// 获取 BeanFactory(调用子类实现的工厂方法)
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory(); // 调用抽象工厂方法
return getBeanFactory();
}
// 另一个工厂方法:返回内部的 BeanFactory 实例
public abstract ConfigurableListableBeanFactory getBeanFactory();
}
// 具体子类:ClassPathXmlApplicationContext
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {
// 实现了父类的抽象工厂方法,创建具体的 BeanFactory
@Override
protected void refreshBeanFactory() throws BeansException {
// 创建 DefaultListableBeanFactory 并加载 Bean 定义
// ...
}
}
模式体现:
AbstractApplicationContext作为抽象工厂 ,声明了refreshBeanFactory()、closeBeanFactory()、getBeanFactory()等工厂方法。ClassPathXmlApplicationContext、AnnotationConfigApplicationContext等子类是具体工厂 ,实现这些工厂方法,返回具体的ConfigurableListableBeanFactory实现(如DefaultListableBeanFactory)。- 客户端(如
refresh()方法)通过模板方法模式调用工厂方法,完成容器的初始化。
3.2.4 FactoryBean 在 MyBatis-Spring 整合中的应用
SqlSessionFactoryBean 是 MyBatis 与 Spring 整合的关键桥梁,它实现了 FactoryBean<SqlSessionFactory> 接口。
java
// org.mybatis.spring.SqlSessionFactoryBean
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
private Resource configLocation;
private DataSource dataSource;
// ... 其他配置属性
// 工厂方法:创建 SqlSessionFactory 产品
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet(); // 确保已初始化
}
return this.sqlSessionFactory;
}
// 返回产品类型
@Override
public Class<? extends SqlSessionFactory> getObjectType() {
return (this.sqlSessionFactory != null
? this.sqlSessionFactory.getClass()
: SqlSessionFactory.class);
}
// 是否单例
@Override
public boolean isSingleton() {
return true;
}
// 初始化方法(Spring 回调),在这里构建 SqlSessionFactory
@Override
public void afterPropertiesSet() throws Exception {
// 解析 MyBatis 配置,创建 SqlSessionFactory
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
// 复杂的构建逻辑:加载 XML 配置、注册 Mapper、设置插件等
// ...
return new DefaultSqlSessionFactory(configuration);
}
}
使用方式(Spring XML 配置):
xml
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml" />
<property name="mapperLocations" value="classpath*:mapper/*.xml" />
</bean>
当 Spring 容器启动时:
- 实例化
SqlSessionFactoryBean。 - 注入
dataSource、configLocation等属性。 - 调用
afterPropertiesSet()构建SqlSessionFactory。 - 当其他 Bean 需要注入
SqlSessionFactory时,容器检测到SqlSessionFactoryBean实现了FactoryBean,于是调用getObject()返回SqlSessionFactory实例,而不是SqlSessionFactoryBean本身。
3.3 SLF4J 日志框架
SLF4J(Simple Logging Facade for Java)是工厂方法模式在日志门面设计中的经典应用。它通过工厂方法动态绑定到具体的日志实现(如 Logback、Log4j)。
3.3.1 ILoggerFactory 接口作为抽象工厂
java
// 抽象工厂接口
package org.slf4j;
public interface ILoggerFactory {
// 工厂方法:根据名称获取 Logger 产品
Logger getLogger(String name);
}
// 抽象产品接口
public interface Logger {
void info(String msg);
void debug(String msg);
void error(String msg);
// ... 其他日志方法
}
3.3.2 LoggerFactory 的 bind() 机制剖析
LoggerFactory 是客户端获取 Logger 的入口,它在类加载时通过静态绑定找到具体的 ILoggerFactory 实现。
java
// org.slf4j.LoggerFactory 简化源码
public final class LoggerFactory {
private static ILoggerFactory iLoggerFactory;
// 静态初始化块:绑定具体工厂
static {
iLoggerFactory = bind();
}
// 核心绑定方法:寻找并实例化 ILoggerFactory 实现
private static ILoggerFactory bind() {
try {
// 1. 尝试从类路径中查找 StaticLoggerBinder(由具体日志实现提供)
ClassLoader cl = LoggerFactory.class.getClassLoader();
Class<?> binderClass = cl.loadClass("org.slf4j.impl.StaticLoggerBinder");
// 2. 通过反射获取单例并调用 getLoggerFactory()
Object binder = binderClass.getMethod("getSingleton").invoke(null);
ILoggerFactory factory = (ILoggerFactory)
binder.getClass().getMethod("getLoggerFactory").invoke(binder);
return factory;
} catch (Exception e) {
// 3. 如果找不到绑定,返回 NOPLoggerFactory(空实现)
return new NOPLoggerFactory();
}
}
// 客户端调用入口
public static Logger getLogger(String name) {
return iLoggerFactory.getLogger(name);
}
}
3.3.3 StaticLoggerBinder 的运行时绑定
以 Logback 为例,它提供了 StaticLoggerBinder 类:
java
// ch.qos.logback.classic.util.StaticLoggerBinder (Logback 实现)
package org.slf4j.impl; // 注意:必须位于此包下
public class StaticLoggerBinder {
private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
public static StaticLoggerBinder getSingleton() {
return SINGLETON;
}
// 工厂方法:返回具体工厂
public ILoggerFactory getLoggerFactory() {
// 返回 Logback 的 LoggerContext(实现了 ILoggerFactory)
return new LoggerContext();
}
}
// Logback 的具体工厂实现
public class LoggerContext extends ContextBase implements ILoggerFactory {
@Override
public Logger getLogger(String name) {
// 返回 Logback 的 Logger 实现
return new Logger(name);
}
}
绑定机制流程图(使用 flowchart 绘制):
文字说明 : SLF4J 的工厂绑定机制巧妙地运用了工厂方法模式与类加载机制的结合。LoggerFactory 在首次调用时触发静态初始化,通过类路径查找具体日志实现提供的 org.slf4j.impl.StaticLoggerBinder 类。该类充当了具体工厂的提供者 角色,其 getLoggerFactory() 方法返回真正的 ILoggerFactory 实现(如 Logback 的 LoggerContext)。随后,客户端对 getLogger() 的所有调用都委托给该绑定的工厂实例。这种设计使得 SLF4J 在编译时完全不依赖具体日志实现,运行时动态绑定,实现了高度的解耦和灵活性。新增日志实现时,只需提供相应的 StaticLoggerBinder 和 ILoggerFactory 实现即可,完全符合开闭原则。
3.4 JDBC API 中的工厂方法
JDBC 作为 Java 数据库连接的标准 API,大量使用了工厂方法模式。
3.4.1 DriverManager 与 Driver 的关系
DriverManager 管理着一组 Driver 实现,并负责选择合适的驱动来创建连接。Driver 接口中的 connect() 方法可视为工厂方法。
java
// java.sql.Driver 接口
public interface Driver {
// 工厂方法:根据 URL 和属性创建 Connection
Connection connect(String url, java.util.Properties info) throws SQLException;
boolean acceptsURL(String url) throws SQLException;
}
// com.mysql.cj.jdbc.Driver 实现(MySQL)
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
@Override
public Connection connect(String url, Properties info) throws SQLException {
// 创建 MySQL 的 Connection 实现
return new ConnectionImpl(host, port, database, info);
}
}
// DriverManager 类(简化)
public class DriverManager {
private static final CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
public static Connection getConnection(String url) throws SQLException {
// 遍历已注册的 Driver,调用其工厂方法 connect
for (DriverInfo aDriver : registeredDrivers) {
if (aDriver.driver.acceptsURL(url)) {
// 调用 Driver 的工厂方法创建 Connection
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
return con;
}
}
}
throw new SQLException("No suitable driver found for " + url);
}
}
3.4.2 Connection.createStatement() 作为工厂方法
Connection 接口本身也扮演着工厂角色,其 createStatement() 方法是典型的工厂方法。
java
public interface Connection {
// 工厂方法:创建 Statement 产品
Statement createStatement() throws SQLException;
// 工厂方法:创建 PreparedStatement 产品
PreparedStatement prepareStatement(String sql) throws SQLException;
// 工厂方法:创建 CallableStatement 产品
CallableStatement prepareCall(String sql) throws SQLException;
}
// MySQL 的 ConnectionImpl 实现
public class ConnectionImpl implements Connection {
@Override
public Statement createStatement() throws SQLException {
// 返回 MySQL 的 Statement 实现
return new StatementImpl(this);
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return new PreparedStatementImpl(this, sql);
}
}
这里体现了工厂方法模式的嵌套使用:
DriverManager使用Driver.connect()创建Connection(一级工厂方法)。Connection使用createStatement()创建Statement(二级工厂方法)。ResultSet则通过Statement.executeQuery()创建(三级工厂方法)。
四、对比辨析
工厂方法模式常与其他创建型模式或设计手法混淆,清晰的对比有助于在合适场景下做出正确选择。
4.1 工厂方法模式 vs 简单工厂模式
| 对比维度 | 简单工厂模式 | 工厂方法模式 |
|---|---|---|
| 开闭原则 | 违背:新增产品时需修改工厂类的判断逻辑 | 遵循:新增产品只需新增具体工厂类,无需修改已有代码 |
| 扩展性 | 差:所有创建逻辑集中在一个类中,产品种类增多时工厂类臃肿 | 好:每个具体产品对应一个具体工厂,职责单一,易于扩展 |
| 复杂度 | 低:仅需一个工厂类 | 中:类和对象数量增多,需要理解抽象与多态 |
| 设计模式地位 | 非 GoF 标准模式,一种编程习惯 | GoF 标准创建型模式 |
| 适用场景 | 产品种类少、变动不频繁的小型系统;客户端只关心传入参数 | 产品种类多、频繁扩展的系统;框架设计中的扩展点 |
结论:当系统产品类型较少且相对稳定时,简单工厂因其简洁性仍有价值;但当系统面临频繁扩展时,工厂方法模式是更符合工程化要求的选择。
4.2 工厂方法模式 vs 抽象工厂模式
| 对比维度 | 工厂方法模式 | 抽象工厂模式 |
|---|---|---|
| 产品维度 | 单一产品等级结构(如 Logger 接口及其子类 FileLogger、ConsoleLogger) | 多个产品等级结构组成的产品族(如 Button 和 TextField 分别有 Windows 和 Mac 风格) |
| 工厂角色数量 | 一个抽象工厂对应多个具体工厂,每个具体工厂创建一个产品 | 一个抽象工厂对应多个具体工厂,每个具体工厂创建一族产品 |
| 扩展产品族 | 不涉及产品族概念 | 增加新产品族(如增加 Linux 风格)较容易,只需增加对应具体工厂 |
| 扩展产品等级 | 增加新产品等级(如新增 DatabaseLogger)容易,增加新的具体工厂即可 | 增加新产品等级(如新增 Menu 组件)困难,需修改抽象工厂接口及所有具体工厂 |
| 结构复杂度 | 相对简单 | 更复杂,涉及多个产品接口和工厂接口 |
| 升级路径 | 当系统出现多个相关联的产品等级时,可演进为抽象工厂模式 | 若产品族中只有一个产品等级,可简化为工厂方法模式 |
何时选用工厂方法,何时升级为抽象工厂?
- 当系统只需要创建一类产品,且未来扩展主要是在这一类的子类上时,使用工厂方法模式即可。
- 当系统需要创建多类相互关联或依赖的产品(例如 UI 主题中的按钮、文本框、下拉框),且这些产品需要保证风格一致性(不能混搭 Windows 按钮和 Mac 文本框),则应使用抽象工厂模式。
4.3 工厂方法 vs 模板方法模式
工厂方法模式常与模板方法模式结合使用,因为工厂方法本身就是模板方法模式中的"钩子方法"的一个特例。
以 Spring AbstractApplicationContext 为例:
java
public abstract class AbstractApplicationContext {
// 模板方法:定义了容器刷新的骨架算法
public void refresh() {
// 步骤1:准备刷新
prepareRefresh();
// 步骤2:获取 BeanFactory(调用工厂方法)
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 步骤3:准备 BeanFactory
prepareBeanFactory(beanFactory);
// 步骤4:后置处理
postProcessBeanFactory(beanFactory);
// 步骤5:调用 BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
// ...
}
// 工厂方法,同时也是模板方法中的钩子方法
protected abstract ConfigurableListableBeanFactory obtainFreshBeanFactory();
}
- 模板方法模式 :
refresh()定义了算法骨架,固定了执行顺序,某些步骤(如obtainFreshBeanFactory())延迟到子类实现。 - 工厂方法模式 :
obtainFreshBeanFactory()是一个创建ConfigurableListableBeanFactory对象的工厂方法。
协同关系 :模板方法模式负责控制业务流程的骨架 ,工厂方法模式负责创建流程所需的对象。两者结合可以构建出高度灵活且可扩展的框架。
4.4 静态工厂方法 vs 工厂方法模式
静态工厂方法(如 Integer.valueOf())虽然名字中包含"工厂",但其设计意图与工厂方法模式有本质区别。
| 对比维度 | 静态工厂方法(如 Integer.valueOf) |
工厂方法模式 |
|---|---|---|
| 设计意图 | 提供比构造器更灵活的对象创建方式,如缓存实例、返回子类型、有名称的构造逻辑 | 将对象创建延迟到子类,实现客户端与具体产品的解耦和扩展 |
| 多态性 | 静态方法不具备多态性,无法被子类重写(只能隐藏) | 实例方法具备多态性,子类可以重写以改变创建行为 |
| 继承与扩展 | 无法通过继承改变创建逻辑 | 可通过新增子类工厂来扩展新的产品创建逻辑 |
| 模式地位 | 一种编码技巧,非 GoF 设计模式 | GoF 创建型设计模式 |
| 典型应用 | Boolean.valueOf()、Optional.of()、Collections.emptyList() |
Calendar.getInstance()、FactoryBean.getObject() |
静态工厂方法的价值:
- 实例控制 :如
Integer.valueOf()缓存了 -128~127 的整数,减少对象创建开销。 - 语义清晰 :
BigInteger.probablePrime()比构造器更能表达意图。 - 返回子类型 :
Collections.unmodifiableList()返回不可修改的 List 实现。
本质区别 :静态工厂方法是一种对象创建技巧 ,而工厂方法模式是一种可扩展的架构设计模式。
五、适用场景分析
工厂方法模式在以下场景中具有显著优势,我们逐一分析其技术理由。
5.1 客户端不关心具体产品类名,只需知道工厂名即可获取对象
场景描述 :在日志记录场景中,开发者只需调用 LoggerFactory.getLogger(MyClass.class) 即可获取 Logger,无需关心底层是 Logback 还是 Log4j 实现。
技术理由:
- 工厂方法将具体产品类的名称封装在具体工厂内部,客户端仅依赖抽象接口。
- 产品类的变化(如更换日志实现)不会波及客户端代码。
- 符合依赖倒置原则:客户端依赖抽象(ILoggerFactory、Logger),而非具体实现。
5.2 一个类希望通过其子类来指定创建哪个对象
场景描述 :在框架开发中,框架定义抽象类,用户通过继承并实现工厂方法来定制对象的创建。例如 Spring 的 AbstractApplicationContext 让子类决定创建何种 BeanFactory。
技术理由:
- 父类将创建逻辑延迟到子类,使得父类的业务逻辑(如容器刷新流程)保持稳定,而变化部分(具体 BeanFactory 的创建)由子类控制。
- 这是一种典型的"好莱坞原则"------"Don't call us, we'll call you"。父类调用子类实现的工厂方法,控制反转得以实现。
5.3 将创建对象的职责委托给多个子类中的某一个,实现局部化管理
场景描述:在一个支持多种支付方式的系统中,不同支付渠道(微信、支付宝、银行卡)需要不同的处理逻辑对象。可以为每种支付方式提供一个工厂子类,负责创建对应的处理器。
技术理由:
- 每个具体工厂类职责单一,仅负责一种产品的创建,符合单一职责原则。
- 新增支付方式时,只需新增一个具体工厂类,不影响已有支付方式的代码。
- 工厂类可以被独立测试和维护。
5.4 框架与组件的扩展点设计
场景描述 :Spring 的 FactoryBean、SLF4J 的 ILoggerFactory、MyBatis 的 ObjectFactory 等都是框架提供的扩展点,允许开发者定制对象创建逻辑。
技术理由:
- 框架定义抽象工厂接口,开发者提供具体实现,框架通过反射或 SPI 机制加载。
- 这是一种典型的微内核架构模式:核心系统稳定,扩展通过插件(具体工厂)动态插入。
- 工厂方法模式为框架提供了"可插拔"的能力,是框架灵活性的基石。
5.5 数据库驱动加载场景
场景描述 :JDBC 的 DriverManager 需要支持 MySQL、Oracle、PostgreSQL 等多种数据库驱动。
技术理由:
- 每种数据库驱动实现
Driver接口,并提供创建Connection的工厂方法。 DriverManager在运行时遍历已注册的Driver,调用其工厂方法尝试连接。- 新增数据库支持时,只需将新驱动的 jar 包放入类路径,驱动类在静态初始化块中向
DriverManager注册,完全无需修改 JDK 核心代码。 - 这是工厂方法模式与 SPI(Service Provider Interface)机制结合的典范。
5.6 测试场景:利用工厂方法实现 Mock 对象的注入
场景描述:在单元测试中,需要将被测对象依赖的外部服务(如支付网关、短信服务)替换为 Mock 对象。
技术理由:
- 如果代码中使用工厂方法模式获取依赖对象,则可以在测试时提供一个返回 Mock 对象的测试工厂子类。
- 通过依赖注入或配置切换工厂实现,实现测试替身的无缝替换。
代码示例:
java
// 生产代码
interface PaymentService {
void pay(Order order);
}
interface PaymentServiceFactory {
PaymentService create();
}
class OrderProcessor {
private PaymentServiceFactory factory;
public OrderProcessor(PaymentServiceFactory factory) {
this.factory = factory;
}
public void process(Order order) {
PaymentService service = factory.create();
service.pay(order);
}
}
// 测试代码
class MockPaymentService implements PaymentService {
// Mock 实现
}
class TestPaymentServiceFactory implements PaymentServiceFactory {
public PaymentService create() {
return new MockPaymentService();
}
}
// 测试方法
@Test
public void testOrderProcessing() {
OrderProcessor processor = new OrderProcessor(new TestPaymentServiceFactory());
processor.process(order);
// 断言...
}
六、面试题精选与专家级解答
以下精选了 10 道与工厂方法模式相关的专家级面试题,并提供深度解析。
6.1 简单工厂、工厂方法、抽象工厂三者有什么区别?分别在什么场景下使用?
解答:
| 模式 | 定义 | 角色结构 | 适用场景 |
|---|---|---|---|
| 简单工厂 | 一个工厂类根据传入参数决定创建哪种产品实例 | 一个工厂类 + 一个抽象产品 + 多个具体产品 | 产品种类少、变动不频繁;客户端只关心参数,无需暴露创建细节 |
| 工厂方法 | 定义创建对象的接口,让子类决定实例化哪个类 | 一个抽象工厂 + 多个具体工厂;一个抽象产品 + 多个具体产品 | 产品种类多且需频繁扩展;框架需提供扩展点;客户端依赖抽象接口 |
| 抽象工厂 | 提供创建一系列相关或依赖对象的接口,而无需指定具体类 | 一个抽象工厂 + 多个具体工厂;多个抽象产品 + 多组具体产品(产品族) | 系统需要创建多个相关联的产品族,并保证产品间的兼容性(如跨平台 UI 组件) |
选择原则:
- 若只有一个产品等级,未来扩展主要是增加新的产品子类,用工厂方法。
- 若有多个产品等级且产品之间存在关联约束,用抽象工厂。
- 简单工厂可作为过渡或辅助手段,但当系统变复杂时应及时重构为工厂方法。
6.2 工厂方法模式是如何体现开闭原则的?简单工厂为何违背开闭原则?
解答:
- 工厂方法模式体现开闭原则:该模式将创建逻辑分散到各个具体工厂子类中。当需要新增一种产品时,我们只需要增加一个新的具体产品类和一个新的具体工厂类,而无需修改已有的抽象工厂接口、已有的具体工厂类或客户端代码。因此,它对扩展开放,对修改关闭。
- 简单工厂违背开闭原则 :简单工厂将所有创建逻辑集中在一个工厂类的单一方法(通常包含
if-else或switch-case)中。新增产品时,必须修改这个工厂方法,添加新的条件分支。这直接违反了"对修改关闭"的原则。随着产品种类的增加,工厂方法会变得冗长且难以维护。
6.3 JDK 中哪些地方使用了工厂方法模式?请至少列举三个并分析其实现细节
解答:
java.util.Calendar.getInstance():Calendar是抽象产品,createCalendar()是工厂方法,根据Locale返回GregorianCalendar或JapaneseImperialCalendar。java.util.Collection.iterator():Collection接口声明工厂方法iterator(),ArrayList、LinkedList等具体集合类重写该方法,返回各自内部实现的Iterator。java.net.URLStreamHandlerFactory.createURLStreamHandler():URLStreamHandlerFactory是抽象工厂接口,其createURLStreamHandler()是工厂方法。URL类通过它获取协议处理器,支持 HTTP、FTP、自定义协议。javax.xml.parsers.DocumentBuilderFactory.newInstance():抽象工厂模式与工厂方法模式的结合,用于创建DocumentBuilder。
6.4 Spring 中的 FactoryBean 和 BeanFactory 有什么区别?分别体现了什么设计模式?
解答:
- BeanFactory :
- 是 Spring IoC 容器的根接口,负责管理 Bean 的完整生命周期(创建、装配、销毁)。
- 它是一个容器,是工厂方法模式的使用者,而非模式中的角色。
- 体现了 IoC 容器模式 和依赖注入。
- FactoryBean :
- 是一个特殊的 Bean,用于封装复杂 Bean 的创建逻辑。
- 它是一个具体工厂 ,
getObject()方法是工厂方法。 - 体现了工厂方法模式。
- 区别总结 :
BeanFactory是创建和管理所有 Bean 的工厂容器 ;FactoryBean是用于创建特定类型 Bean 的工厂 Bean ,它本身也受BeanFactory管理。
6.5 SLF4J 是如何通过工厂方法模式实现日志框架的动态绑定的?
解答: SLF4J 通过以下机制实现动态绑定:
- 抽象工厂 :
ILoggerFactory接口,声明getLogger(String)工厂方法。 - 具体工厂 :每个日志实现(Logback、Log4j)提供各自的
ILoggerFactory实现(如 Logback 的LoggerContext)。 - 绑定器 :日志实现必须提供一个名为
org.slf4j.impl.StaticLoggerBinder的类,该类提供静态方法getSingleton()返回绑定器实例,绑定器实例的getLoggerFactory()方法返回具体工厂。 - 客户端入口 :
LoggerFactory类在静态初始化时通过类加载器查找StaticLoggerBinder,反射调用以获取ILoggerFactory实例并持有。 - 委托调用 :客户端调用
LoggerFactory.getLogger()时,实际委托给持有的ILoggerFactory实例。
模式体现 :ILoggerFactory 是抽象工厂,LoggerFactory 是工厂方法的使用者(并承担了简单工厂的职责),StaticLoggerBinder 充当了工厂提供者的角色。整个机制完全符合开闭原则:新增日志实现只需提供对应的绑定器和工厂实现,无需修改 SLF4J 核心代码。
6.6 工厂方法模式与依赖注入(DI)有何关系?能否用 DI 替代工厂方法?
解答:
- 关系 :两者都是实现控制反转(IoC) 的手段,目的都是解耦对象的创建与使用。
- 工厂方法模式 :通过继承和多态实现创建逻辑的延迟,属于类型级的解耦。
- 依赖注入 :通过外部容器或调用者将依赖对象注入到目标对象中,属于实例级的解耦,通常配合 IoC 容器使用。
- 能否替代 :
- DI 可以替代部分工厂方法的场景 :当对象创建不涉及复杂逻辑或运行时动态决策时,DI 更简洁(如通过
@Autowired注入一个单例服务)。 - DI 无法完全替代工厂方法 :在需要运行时根据参数决定创建哪个产品 (如
Calendar.getInstance(Locale))、创建过程极其复杂 (如 MyBatis 的SqlSessionFactory)、需要返回多种不同生命周期的对象 (如每次调用工厂方法都返回新实例)时,工厂方法模式仍是必要的。事实上,Spring 的FactoryBean正是 DI 容器与工厂方法模式的结合。
- DI 可以替代部分工厂方法的场景 :当对象创建不涉及复杂逻辑或运行时动态决策时,DI 更简洁(如通过
6.7 工厂方法模式中的工厂类是否可以设计为单例?如果可以,如何结合?
解答 : 可以,且在实际应用中很常见。
- 具体工厂设计为单例:大多数情况下,具体工厂类是无状态的,其唯一职责是创建产品对象。因此,将具体工厂设计为单例可以避免重复创建工厂实例,节省资源。
- 结合方式 :
- 懒汉式/饿汉式单例:在具体工厂类内部实现单例模式。
- 枚举单例:使用枚举实现工厂。
- 结合 IoC 容器 :将工厂 Bean 的作用域设置为
singleton(Spring 默认)。
- 示例:
java
public class FileLoggerCreator extends LoggerCreator {
private static final FileLoggerCreator INSTANCE = new FileLoggerCreator();
private FileLoggerCreator() {}
public static FileLoggerCreator getInstance() {
return INSTANCE;
}
@Override
public Logger createLogger() {
return new FileLogger(); // 每次都创建新产品(非单例)
}
}
注意:工厂本身是单例,但它创建的产品不一定是单例。工厂方法可以根据需要每次都返回新实例(原型模式)或返回缓存的单例。
6.8 当产品族和产品等级同时需要扩展时,工厂方法模式存在什么局限?如何演进为抽象工厂模式?
解答:
- 工厂方法模式的局限 : 工厂方法模式只针对单一产品等级结构 。当系统需要同时支持多个产品等级(例如,UI 组件库中的
Button和TextField),且这些产品等级之间存在风格约束(如 Windows 风格的按钮必须搭配 Windows 风格的文本框),工厂方法模式就显得力不从心了。如果强行使用工厂方法,可能会导致客户端需要组合多个不同的工厂,且无法保证风格的一致性。 - 演进为抽象工厂模式 :
- 识别系统中的产品族 :将风格相关的产品归为一族(如
WindowsButton和WindowsTextField属于 Windows 族)。 - 定义抽象工厂接口:该接口包含针对每个产品等级的工厂方法,例如
createButton()和createTextField()。 - 实现具体工厂:为每个产品族提供一个具体工厂,负责创建该族内的所有产品。
- 客户端依赖抽象工厂接口,运行时注入具体的工厂实现,从而保证产品族的一致性。
- 识别系统中的产品族 :将风格相关的产品归为一族(如
6.9 工厂方法模式在单元测试中有什么优势?举例说明
解答:
- 优势 :
- 可测试性:通过替换具体工厂,可以轻松将被测对象依赖的复杂组件替换为测试替身(Mock 或 Stub)。
- 隔离性:测试时无需关心真实产品的复杂构造过程(如数据库连接、网络请求),只需关注被测对象的业务逻辑。
- 灵活性:可以为不同的测试场景提供不同的测试工厂实现。
- 举例 : 如前文 5.6 节所示,
OrderProcessor依赖PaymentServiceFactory接口。在单元测试中,我们注入TestPaymentServiceFactory,它返回MockPaymentService。这样,测试OrderProcessor.process()时无需真正调用支付网关,只需验证 Mock 对象的pay()方法是否被正确调用,并可模拟各种支付成功/失败的返回情况。
6.10 静态工厂方法(如 Integer.valueOf)与工厂方法模式的设计哲学有何本质区别?
解答:
- 设计哲学的本质区别 :
- 静态工厂方法 :是一种对象创建技巧 ,其核心目的是替代构造器 ,提供更有表达力、更高效或更灵活的对象获取方式。它关注的是如何更好地创建单个对象 ,侧重于语法和性能层面的优化。它不具备设计模式的扩展性诉求。
- 工厂方法模式 :是一种面向对象设计模式 ,其核心目的是解耦 。它通过抽象和多态,将"创建什么对象"的决定权从使用者的代码中剥离,交给子类去实现。它关注的是系统架构层面的扩展性 ,侧重于应对变化。
- 具体体现 :
Integer.valueOf(int)缓存了常用数值,避免重复创建,但它无法改变"创建的是 Integer 对象"这一事实,也无法通过继承来改变其创建逻辑(因为它是静态方法)。- 工厂方法模式中的工厂方法(如
createLogger())是实例方法,可以被重写,从而使得子类能够完全改变所创建的产品类型,这为系统的扩展提供了无限的灵活性。
七、图表要求(补充满足)
图表 a:工厂方法模式标准 UML 类图
已在 1.2 节 使用 Mermaid flowchart 提供,并附有 200 字以上说明。
图表 b:简单工厂演进为工厂方法模式的重构过程类图对比
+createLogger(type)"] SF_Product["<
Logger"] SF_File["FileLogger"] SF_Console["ConsoleLogger"] SF_Client --> SF_Factory SF_Client --> SF_Product SF_Factory -.->|"<
LoggerCreator
+createLogger() Logger"] FM_FileCreator["FileLoggerCreator
+createLogger()"] FM_ConsoleCreator["ConsoleLoggerCreator
+createLogger()"] FM_DatabaseCreator["DatabaseLoggerCreator
+createLogger()"] FM_Product["<
Logger"] FM_File["FileLogger"] FM_Console["ConsoleLogger"] FM_Database["DatabaseLogger"] FM_Client --> FM_Creator FM_Client --> FM_Product FM_FileCreator -->|"<
图表演进说明:
上图对比了从简单工厂到工厂方法模式的重构过程。左侧的简单工厂结构中,LoggerFactory 类承担了所有产品的创建职责,其 createLogger 方法通过 if-else 分支判断需要实例化哪个具体产品。当系统需要新增 DatabaseLogger 时(图中红色虚线框所示),必须修改 LoggerFactory 的代码,增加了出错风险且违背开闭原则。
右侧的工厂方法结构则将创建职责分散到了各个具体工厂子类中。LoggerCreator 抽象类声明了 createLogger() 工厂方法,FileLoggerCreator、ConsoleLoggerCreator 和新增的 DatabaseLoggerCreator 分别实现该方法,返回各自对应的产品。客户端(Client)持有 LoggerCreator 的引用,通过多态机制在运行时确定具体创建哪种产品。新增 DatabaseLogger 时,我们只需增加 DatabaseLogger 类和 DatabaseLoggerCreator 类,无需触碰任何已有代码,完美遵循了开闭原则。重构后,系统的扩展点从"修改工厂类的内部逻辑"转移到了"新增工厂子类",这正是工厂方法模式的核心价值。
图表 c:SLF4J 日志框架工厂绑定的流程图
已在 3.3.3 节 使用 Mermaid flowchart 提供,并附有详细说明。
图表 d:Spring FactoryBean 创建 Bean 的时序图
鉴于时序图使用 flowchart 语法不便表达,此处使用 Mermaid 的 sequence 语法绘制,符合要求中的"若 flowchart 不便于表达时序逻辑可改用 sequence 语法"。
时序图说明:
本时序图详细描绘了 Spring 容器在处理 FactoryBean 类型 Bean 时的内部调用流程。当客户端通过 getBean("sqlSessionFactory") 请求一个 Bean 时,Spring 容器首先检查该 Bean 的定义,识别出它是一个 FactoryBean。容器随后执行以下步骤:
- 实例化 FactoryBean :如果
SqlSessionFactoryBean尚未实例化,容器首先创建该工厂 Bean 的实例。 - 依赖注入 :容器将配置的属性(如
dataSource、configLocation)注入到工厂 Bean 实例中。 - 初始化回调 :由于
SqlSessionFactoryBean实现了InitializingBean接口,容器调用其afterPropertiesSet()方法。在该方法内部,SqlSessionFactoryBean执行复杂的构建逻辑,调用buildSqlSessionFactory()创建真正的目标对象------DefaultSqlSessionFactory(即SqlSessionFactory的实现类),并将它保存在成员变量中。 - 获取产品 :当容器准备将 Bean 返回给客户端时,检测到该 Bean 是
FactoryBean类型,于是不直接返回SqlSessionFactoryBean本身,而是调用其getObject()方法。getObject()方法返回之前构建好的SqlSessionFactory实例。 - 返回产品 :容器将
SqlSessionFactory实例返回给客户端。
整个过程中,客户端完全不感知 SqlSessionFactory 的复杂创建细节,只需声明对 SqlSessionFactory 的依赖即可。FactoryBean 充当了工厂方法模式中的具体工厂 ,其 getObject() 是核心的工厂方法 ,而 Spring 容器则扮演了工厂方法模式的使用者和管理者角色。这种设计将对象创建的控制权从容器转移到了开发者编写的 FactoryBean 代码中,极大地增强了 Bean 创建的灵活性和可定制性。
八、结语
工厂方法模式是面向对象设计中一颗璀璨的明珠,它以简洁而优雅的方式解决了对象创建中的耦合问题。从最朴素的直接 new,到简单工厂的初步封装,再到工厂方法模式的彻底解耦,我们见证了一种设计思想如何一步步演进,最终在 JDK、Spring、SLF4J 等工业级框架中绽放光彩。
掌握工厂方法模式,不仅意味着能够正确画出 UML 类图、写出标准代码,更意味着能够洞察框架设计的深层意图,理解那些看似平凡的 getInstance() 方法背后所蕴含的架构智慧。当你在面试中从容辨析 BeanFactory 与 FactoryBean,当你在架构选型时果断选择工厂方法而非简单工厂,你已经从一个 API 调用者,成长为了一个真正的系统设计师。
希望本文能够成为你深入理解工厂方法模式的坚实阶梯。设计模式的学习永无止境,愿你在模式的世界里不断探索,构建出更加灵活、健壮、优雅的软件系统。