创建型模式-工厂方法模式

概述

在面向对象设计的广阔天地中,对象创建是一个永恒的话题。工厂方法模式(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 类图:

classDiagram class Client class Product { <> +operation() } class Creator { <> +factoryMethod() Product +someOperation() } class ConcreteProductA { +operation() } class ConcreteProductB { +operation() } class ConcreteCreatorA { +factoryMethod() Product } class ConcreteCreatorB { +factoryMethod() Product } Client --> Creator Client --> Product Creator --> Product : creates ConcreteCreatorA --|> Creator ConcreteCreatorB --|> Creator ConcreteCreatorA ..> ConcreteProductA : creates ConcreteCreatorB ..> ConcreteProductB : creates ConcreteProductA --|> Product ConcreteProductB --|> Product Creator ..> ConcreteProductA : may create (default)

1.3 类图详细文字说明

上方的 flowchart 类图清晰地展示了工厂方法模式的四类核心角色及其相互关系。

抽象产品(Product) :位于图的左上角,是一个接口(或抽象类),定义了所有具体产品必须实现的操作契约。客户端仅依赖于此接口,从而避免了对具体产品类的直接引用。在图中,Product 声明了 operation() 方法,这是所有产品的公共行为。

具体产品(ConcreteProduct) :位于图的下方,ConcreteProductAConcreteProductB 实现了 Product 接口。每个具体产品都提供了 operation() 的具体实现。它们是工厂方法最终要创建的目标对象。注意具体产品与抽象产品之间的泛化关系(--|> 箭头)。

抽象工厂(Creator) :位于图的中央偏右,是一个抽象类。它声明了工厂方法 factoryMethod(),该方法返回一个 Product 类型的对象。同时,抽象工厂可能包含一些依赖产品对象的业务逻辑方法(如 someOperation()),这些方法会调用工厂方法来获取产品实例,然后执行后续操作。Creator 与 Product 之间的虚线依赖箭头(..>)表示工厂方法返回的是 Product 类型。

具体工厂(ConcreteCreator) :位于图的下方,ConcreteCreatorAConcreteCreatorB 继承自 Creator,并重写了工厂方法,分别返回 ConcreteProductAConcreteProductB 的实例。具体工厂与具体产品之间存在创建依赖关系(..> 虚线箭头标注 <<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.");
        }
    }
}

耦合问题分析

  • 客户端代码直接依赖 FileLoggerConsoleLogger 具体类。
  • 新增日志类型(如 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 时,我们只需新增 DatabaseLoggerDatabaseLoggerCreator 两个类,无需修改已有的 FileLoggerCreatorConsoleLoggerCreator 等任何现有代码。
  • 单一职责:每个具体工厂类只负责创建一种产品,职责清晰。
  • 扩展性:未来增加新的日志类型时,扩展成本极低。

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 抽象类。
  • 具体产品GregorianCalendarJapaneseImperialCalendar 等。
  • 工厂方法createCalendar 方法,根据 LocaleTimeZone 参数决定返回哪个具体实现。
  • 客户端代码 :调用 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()
  • 每个具体的集合类(ArrayListLinkedListHashSet 等)都实现了该方法,返回适合其内部数据结构的迭代器。
  • 客户端代码通过 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() 的产品
典型实现 DefaultListableBeanFactoryXmlWebApplicationContext SqlSessionFactoryBeanProxyFactoryBean
创建对象方式 委托给 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() 等工厂方法。
  • ClassPathXmlApplicationContextAnnotationConfigApplicationContext 等子类是具体工厂 ,实现这些工厂方法,返回具体的 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 容器启动时:

  1. 实例化 SqlSessionFactoryBean
  2. 注入 dataSourceconfigLocation 等属性。
  3. 调用 afterPropertiesSet() 构建 SqlSessionFactory
  4. 当其他 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 绘制):

flowchart TD A[客户端调用 LoggerFactory.getLogger] --> B{首次调用?} B -->|是| C[执行静态初始化块 bind] B -->|否| F[直接使用已绑定的 ILoggerFactory] C --> D[ClassLoader 查找 org.slf4j.impl.StaticLoggerBinder] D --> E{是否找到?} E -->|找到| G[反射调用 getSingleton 获取 StaticLoggerBinder 实例] G --> H[调用 getLoggerFactory 获取具体 ILoggerFactory] H --> I[绑定到 iLoggerFactory 静态变量] E -->|未找到| J[返回 NOPLoggerFactory 空实现] J --> I I --> F F --> K[调用 iLoggerFactory.getLogger 返回具体 Logger] K --> L[客户端获得 Logger 实例]

文字说明 : SLF4J 的工厂绑定机制巧妙地运用了工厂方法模式与类加载机制的结合。LoggerFactory 在首次调用时触发静态初始化,通过类路径查找具体日志实现提供的 org.slf4j.impl.StaticLoggerBinder 类。该类充当了具体工厂的提供者 角色,其 getLoggerFactory() 方法返回真正的 ILoggerFactory 实现(如 Logback 的 LoggerContext)。随后,客户端对 getLogger() 的所有调用都委托给该绑定的工厂实例。这种设计使得 SLF4J 在编译时完全不依赖具体日志实现,运行时动态绑定,实现了高度的解耦和灵活性。新增日志实现时,只需提供相应的 StaticLoggerBinderILoggerFactory 实现即可,完全符合开闭原则。

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-elseswitch-case)中。新增产品时,必须修改这个工厂方法,添加新的条件分支。这直接违反了"对修改关闭"的原则。随着产品种类的增加,工厂方法会变得冗长且难以维护。

6.3 JDK 中哪些地方使用了工厂方法模式?请至少列举三个并分析其实现细节

解答

  1. java.util.Calendar.getInstance()Calendar 是抽象产品,createCalendar() 是工厂方法,根据 Locale 返回 GregorianCalendarJapaneseImperialCalendar
  2. java.util.Collection.iterator()Collection 接口声明工厂方法 iterator()ArrayListLinkedList 等具体集合类重写该方法,返回各自内部实现的 Iterator
  3. java.net.URLStreamHandlerFactory.createURLStreamHandler()URLStreamHandlerFactory 是抽象工厂接口,其 createURLStreamHandler() 是工厂方法。URL 类通过它获取协议处理器,支持 HTTP、FTP、自定义协议。
  4. 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 通过以下机制实现动态绑定:

  1. 抽象工厂ILoggerFactory 接口,声明 getLogger(String) 工厂方法。
  2. 具体工厂 :每个日志实现(Logback、Log4j)提供各自的 ILoggerFactory 实现(如 Logback 的 LoggerContext)。
  3. 绑定器 :日志实现必须提供一个名为 org.slf4j.impl.StaticLoggerBinder 的类,该类提供静态方法 getSingleton() 返回绑定器实例,绑定器实例的 getLoggerFactory() 方法返回具体工厂。
  4. 客户端入口LoggerFactory 类在静态初始化时通过类加载器查找 StaticLoggerBinder,反射调用以获取 ILoggerFactory 实例并持有。
  5. 委托调用 :客户端调用 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 容器与工厂方法模式的结合。

6.7 工厂方法模式中的工厂类是否可以设计为单例?如果可以,如何结合?

解答可以,且在实际应用中很常见。

  • 具体工厂设计为单例:大多数情况下,具体工厂类是无状态的,其唯一职责是创建产品对象。因此,将具体工厂设计为单例可以避免重复创建工厂实例,节省资源。
  • 结合方式
    1. 懒汉式/饿汉式单例:在具体工厂类内部实现单例模式。
    2. 枚举单例:使用枚举实现工厂。
    3. 结合 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 组件库中的 ButtonTextField),且这些产品等级之间存在风格约束(如 Windows 风格的按钮必须搭配 Windows 风格的文本框),工厂方法模式就显得力不从心了。如果强行使用工厂方法,可能会导致客户端需要组合多个不同的工厂,且无法保证风格的一致性。
  • 演进为抽象工厂模式
    1. 识别系统中的产品族 :将风格相关的产品归为一族(如 WindowsButtonWindowsTextField 属于 Windows 族)。
    2. 定义抽象工厂接口:该接口包含针对每个产品等级的工厂方法,例如 createButton()createTextField()
    3. 实现具体工厂:为每个产品族提供一个具体工厂,负责创建该族内的所有产品。
    4. 客户端依赖抽象工厂接口,运行时注入具体的工厂实现,从而保证产品族的一致性。

6.9 工厂方法模式在单元测试中有什么优势?举例说明

解答

  • 优势
    1. 可测试性:通过替换具体工厂,可以轻松将被测对象依赖的复杂组件替换为测试替身(Mock 或 Stub)。
    2. 隔离性:测试时无需关心真实产品的复杂构造过程(如数据库连接、网络请求),只需关注被测对象的业务逻辑。
    3. 灵活性:可以为不同的测试场景提供不同的测试工厂实现。
  • 举例 : 如前文 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:简单工厂演进为工厂方法模式的重构过程类图对比

flowchart TD subgraph "重构前_简单工厂" direction TB SF_Client["Client"] SF_Factory["LoggerFactory
+createLogger(type)"] SF_Product["<>
Logger"] SF_File["FileLogger"] SF_Console["ConsoleLogger"] SF_Client --> SF_Factory SF_Client --> SF_Product SF_Factory -.->|"<>"| SF_File SF_Factory -.->|"<>"| SF_Console SF_File -->|"<>"| SF_Product SF_Console -->|"<>"| SF_Product end subgraph "重构后_工厂方法" direction TB FM_Client["Client"] FM_Creator["<>
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 -->|"<>"| FM_Creator FM_ConsoleCreator -->|"<>"| FM_Creator FM_DatabaseCreator -->|"<>"| FM_Creator FM_FileCreator -.->|"<>"| FM_File FM_ConsoleCreator -.->|"<>"| FM_Console FM_DatabaseCreator -.->|"<>"| FM_Database FM_File -->|"<>"| FM_Product FM_Console -->|"<>"| FM_Product FM_Database -->|"<>"| FM_Product end SF_Factory -.->|"重构"| FM_Creator

图表演进说明

上图对比了从简单工厂到工厂方法模式的重构过程。左侧的简单工厂结构中,LoggerFactory 类承担了所有产品的创建职责,其 createLogger 方法通过 if-else 分支判断需要实例化哪个具体产品。当系统需要新增 DatabaseLogger 时(图中红色虚线框所示),必须修改 LoggerFactory 的代码,增加了出错风险且违背开闭原则。

右侧的工厂方法结构则将创建职责分散到了各个具体工厂子类中。LoggerCreator 抽象类声明了 createLogger() 工厂方法,FileLoggerCreatorConsoleLoggerCreator 和新增的 DatabaseLoggerCreator 分别实现该方法,返回各自对应的产品。客户端(Client)持有 LoggerCreator 的引用,通过多态机制在运行时确定具体创建哪种产品。新增 DatabaseLogger 时,我们只需增加 DatabaseLogger 类和 DatabaseLoggerCreator 类,无需触碰任何已有代码,完美遵循了开闭原则。重构后,系统的扩展点从"修改工厂类的内部逻辑"转移到了"新增工厂子类",这正是工厂方法模式的核心价值。

图表 c:SLF4J 日志框架工厂绑定的流程图

已在 3.3.3 节 使用 Mermaid flowchart 提供,并附有详细说明。

图表 d:Spring FactoryBean 创建 Bean 的时序图

鉴于时序图使用 flowchart 语法不便表达,此处使用 Mermaid 的 sequence 语法绘制,符合要求中的"若 flowchart 不便于表达时序逻辑可改用 sequence 语法"。

sequenceDiagram participant Client as 客户端/其他Bean participant Container as Spring IoC容器 participant FactoryBean as SqlSessionFactoryBean participant Product as SqlSessionFactory Client->>Container: getBean("sqlSessionFactory") activate Container Container->>Container: 检查Bean定义,发现是FactoryBean Container->>FactoryBean: 实例化FactoryBean(若未创建) activate FactoryBean FactoryBean-->>Container: FactoryBean实例 deactivate FactoryBean Container->>FactoryBean: 注入属性(dataSource, configLocation等) activate FactoryBean FactoryBean-->>Container: 属性设置完成 deactivate FactoryBean Container->>FactoryBean: 调用InitializingBean.afterPropertiesSet() activate FactoryBean FactoryBean->>FactoryBean: buildSqlSessionFactory() FactoryBean->>Product: 创建DefaultSqlSessionFactory activate Product Product-->>FactoryBean: SqlSessionFactory实例 deactivate Product FactoryBean-->>Container: 初始化完成 deactivate FactoryBean Container->>FactoryBean: 调用getObject() activate FactoryBean FactoryBean-->>Container: 返回SqlSessionFactory实例 deactivate FactoryBean Container-->>Client: 返回SqlSessionFactory Bean deactivate Container

时序图说明

本时序图详细描绘了 Spring 容器在处理 FactoryBean 类型 Bean 时的内部调用流程。当客户端通过 getBean("sqlSessionFactory") 请求一个 Bean 时,Spring 容器首先检查该 Bean 的定义,识别出它是一个 FactoryBean。容器随后执行以下步骤:

  1. 实例化 FactoryBean :如果 SqlSessionFactoryBean 尚未实例化,容器首先创建该工厂 Bean 的实例。
  2. 依赖注入 :容器将配置的属性(如 dataSourceconfigLocation)注入到工厂 Bean 实例中。
  3. 初始化回调 :由于 SqlSessionFactoryBean 实现了 InitializingBean 接口,容器调用其 afterPropertiesSet() 方法。在该方法内部,SqlSessionFactoryBean 执行复杂的构建逻辑,调用 buildSqlSessionFactory() 创建真正的目标对象------DefaultSqlSessionFactory(即 SqlSessionFactory 的实现类),并将它保存在成员变量中。
  4. 获取产品 :当容器准备将 Bean 返回给客户端时,检测到该 Bean 是 FactoryBean 类型,于是不直接返回 SqlSessionFactoryBean 本身,而是调用其 getObject() 方法。getObject() 方法返回之前构建好的 SqlSessionFactory 实例。
  5. 返回产品 :容器将 SqlSessionFactory 实例返回给客户端。

整个过程中,客户端完全不感知 SqlSessionFactory 的复杂创建细节,只需声明对 SqlSessionFactory 的依赖即可。FactoryBean 充当了工厂方法模式中的具体工厂 ,其 getObject() 是核心的工厂方法 ,而 Spring 容器则扮演了工厂方法模式的使用者和管理者角色。这种设计将对象创建的控制权从容器转移到了开发者编写的 FactoryBean 代码中,极大地增强了 Bean 创建的灵活性和可定制性。


八、结语

工厂方法模式是面向对象设计中一颗璀璨的明珠,它以简洁而优雅的方式解决了对象创建中的耦合问题。从最朴素的直接 new,到简单工厂的初步封装,再到工厂方法模式的彻底解耦,我们见证了一种设计思想如何一步步演进,最终在 JDK、Spring、SLF4J 等工业级框架中绽放光彩。

掌握工厂方法模式,不仅意味着能够正确画出 UML 类图、写出标准代码,更意味着能够洞察框架设计的深层意图,理解那些看似平凡的 getInstance() 方法背后所蕴含的架构智慧。当你在面试中从容辨析 BeanFactoryFactoryBean,当你在架构选型时果断选择工厂方法而非简单工厂,你已经从一个 API 调用者,成长为了一个真正的系统设计师。

希望本文能够成为你深入理解工厂方法模式的坚实阶梯。设计模式的学习永无止境,愿你在模式的世界里不断探索,构建出更加灵活、健壮、优雅的软件系统。

相关推荐
敖正炀2 小时前
创造型模式-单例模式
设计模式
ximu_polaris2 小时前
设计模式(C++)-结构型模式-组合模式
c++·设计模式·组合模式
geovindu3 小时前
go: Singleton Pattern
单例模式·设计模式·golang
ximu_polaris3 小时前
设计模式(C++)-结构型模式-外观模式
c++·设计模式·外观模式
不知名的老吴3 小时前
思考:设计模式对前端有用吗?
设计模式·状态模式
ximu_polaris3 小时前
设计模式(C++)-创造型模式-建造者模式
c++·设计模式·建造者模式
likerhood4 小时前
设计模式之建造者模式(Builder Pattern)java版本
java·设计模式·建造者模式
周末也要写八哥4 小时前
前端三大类设计模式学习
学习·设计模式
jump_jump13 小时前
GetX — Flutter 的瑞士军刀,还是过度封装的陷阱?
flutter·设计模式·前端框架