上一章我们探讨了如何保证对象的“唯一性”(单例模式)。但更多时候,我们需要根据不同的情境创建不同类型的对象实例。如果直接在代码中用 new 硬编码具体类名,当需要新增类型或改变创建逻辑时,修改将如同“拆东墙补西墙”,极易违反开闭原则。本文将带你深入理解创建型模式中的“授权代表”——工厂方法模式。我们将揭示它如何通过定义一个创建对象的“标准流程”(接口),并将具体的“生产任务”(实例化)授权给“分厂”(子类)来完成,从而实现创建过程的灵活性与解耦。掌握工厂方法,是构建可扩展、易维护系统的关键一步。


一、问题的提出:当“标准化生产”遭遇“个性化需求”

想象一家大型制造企业,它生产多种不同型号的产品,比如汽车。如果总部(客户端代码)直接负责每一辆具体型号汽车(如“轿车”、“SUV”、“卡车”)的完整制造流程(new Car(), new Suv(), new Truck()),会发生什么?

  • 总部负担过重: 总部需要了解所有型号汽车的详细制造工艺(具体类的构造细节)。
  • 难以扩展: 当需要增加新的车型(如“电动皮卡”)时,必须修改总部的生产调度代码(增加新的 if-elseswitch 判断),这违反了开闭原则 (OCP),风险高且不优雅。
  • 缺乏灵活性: 如果不同地区的“分厂”(子系统或模块)希望在标准流程基础上,对特定车型的制造过程进行微调或使用特定零件,总部集权式的生产方式难以满足这种“本地化”需求。

直接使用 new 进行对象创建,就像是这种“总部集权式生产”。它简单直接,但在面对对象类型多样化、未来可能扩展、创建逻辑复杂或需要子类定制化等场景时,就会显得捉襟见肘,导致代码僵化、耦合紧密、难以维护。我们需要一种更灵活、更符合开闭原则的创建机制。

二、授权的艺术:工厂方法模式的核心定义与意图

工厂方法模式 (Factory Method Pattern) 提供了一种优雅的解决方案。它不再由“总部”直接生产所有产品,而是定义了一套标准的“生产合同”或“建厂规范”(一个用于创建对象的接口或抽象方法),并将实际的“产品制造”任务(调用哪个具体类的构造器来实例化对象)授权给各个“分厂”(子类)去完成。

GoF 的经典意图描述是:“定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。”

这句话点明了工厂方法模式的精髓:

  1. 定义创建接口 (Define an interface for creating an object): 在父类(或接口)中声明一个抽象的“工厂方法”,这个方法负责返回产品对象。它规定了“必须生产一个产品出来”的契约。
  2. 子类决定实例 (Let subclasses decide which class to instantiate): 具体的生产任务由子类来实现这个工厂方法。每个子类根据自己的职责,决定到底 new 出哪一个具体的产品类实例并返回。
  3. 延迟实例化 (Defer instantiation to subclasses): 父类只定义创建的“框架”和“规范”,实际的“实例化动作”被推迟到了子类中执行。

核心角色:

  • Product (产品接口/抽象类): 定义了工厂方法所创建的对象的共同接口。所有具体产品都必须实现这个接口。
  • ConcreteProduct (具体产品类): 实现 Product 接口,是工厂方法实际创建的目标对象。
  • Creator (创建者抽象类/接口): 声明了工厂方法 (factoryMethod()),该方法返回一个 Product 类型的对象。Creator 也可以包含一些依赖于 Product 对象的通用业务逻辑。
  • ConcreteCreator (具体创建者类): 实现了 Creator 中的抽象工厂方法,负责实际创建并返回一个具体的 ConcreteProduct 实例。每个 ConcreteCreator 通常对应一个 ConcreteProduct。

三、解耦的威力:工厂方法模式的适用场景

工厂方法模式在以下场景中特别有用:

  • 编译时无法预知具体对象类型: 当一个类不知道它所必须创建的对象的确切类时,例如,需要根据配置文件、用户输入或运行时条件来决定创建哪种对象。
  • 希望子类指定创建对象: 当一个类希望由它的子类来指定它所创建的对象的时候。这是最典型的应用场景,父类定义框架,子类填充细节(包括创建的对象类型)。
  • 将创建对象的职责委托给辅助子类: 当你需要将创建对象的复杂逻辑或权限控制封装起来,由专门的“工厂子类”负责,主类只负责使用。
  • 框架设计中的扩展点: 框架通常定义了标准的处理流程(模板方法)和对象接口,但将具体对象的创建留给使用者(通过继承框架类并实现工厂方法)来定制。许多框架(如 JDBC、java.util.Calendar 的某些用法)都体现了工厂方法的思想。

四、招式详解:工厂方法模式的 Java 实现

我们通过一个简单的日志记录器示例来演示工厂方法模式的实现。假设我们需要根据不同的场景(如文件记录、数据库记录)创建不同类型的日志记录器。

1. 定义产品接口 (Product):

/**
 * 产品接口:定义了所有日志记录器都需要实现的功能
 */
interface Logger {
    void log(String message);
}

2. 创建具体产品类 (ConcreteProduct):

/**
 * 具体产品A:文件日志记录器
 */
class FileLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("【文件日志】:" + message);
        // ... 实际写入文件的逻辑 ...
    }
}

/**
 * 具体产品B:数据库日志记录器
 */
class DatabaseLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("【数据库日志】:" + message);
        // ... 实际写入数据库的逻辑 ...
    }
}

// 如果未来需要控制台日志记录器,只需添加新类:
class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("【控制台日志】:" + message);
    }
}

3. 定义创建者抽象类 (Creator):

/**
 * 创建者抽象类:定义了创建 Logger 的工厂方法(抽象)
 *              也可以包含一些使用 Logger 的通用业务逻辑
 */
abstract class LoggerFactory {

    // 工厂方法:声明为抽象,由子类实现来创建具体 Logger
    protected abstract Logger createLogger();

    // 使用产品的通用方法 (示例)
    public void writeLog(String message) {
        // 先通过工厂方法获取 Logger 对象
        Logger logger = createLogger();
        // 再使用 Logger 对象记录日志
        logger.log(message);
    }
}

4. 创建具体创建者类 (ConcreteCreator):

/**
 * 具体创建者A:专门用于创建 FileLogger 的工厂
 */
class FileLoggerFactory extends LoggerFactory {
    @Override
    protected Logger createLogger() {
        // 负责创建并返回 FileLogger 实例
        System.out.println("文件日志工厂正在创建 FileLogger...");
        return new FileLogger();
    }
}

/**
 * 具体创建者B:专门用于创建 DatabaseLogger 的工厂
 */
class DatabaseLoggerFactory extends LoggerFactory {
    @Override
    protected Logger createLogger() {
        // 负责创建并返回 DatabaseLogger 实例
        System.out.println("数据库日志工厂正在创建 DatabaseLogger...");
        return new DatabaseLogger();
    }
}

// 如果新增了 ConsoleLogger,只需对应增加 ConsoleLoggerFactory
class ConsoleLoggerFactory extends LoggerFactory {
    @Override
    protected Logger createLogger() {
        System.out.println("控制台日志工厂正在创建 ConsoleLogger...");
        return new ConsoleLogger();
    }
}

5. 客户端使用:

public class FactoryMethodClient {
    public static void main(String[] args) {
        // 需要文件日志?使用文件日志工厂
        LoggerFactory fileFactory = new FileLoggerFactory();
        fileFactory.writeLog("这是一条需要记录到文件的重要信息。");

        System.out.println("--------------------");

        // 需要数据库日志?使用数据库日志工厂
        LoggerFactory dbFactory = new DatabaseLoggerFactory();
        dbFactory.writeLog("这是一条需要存入数据库的操作记录。");

        System.out.println("--------------------");

        // 需要控制台日志?使用控制台日志工厂 (轻松扩展!)
        LoggerFactory consoleFactory = new ConsoleLoggerFactory();
        consoleFactory.writeLog("这是一条调试信息,输出到控制台即可。");

        // 关键:客户端代码 (main方法) 只与 LoggerFactory 和 Logger 接口交互,
        //      完全不知道具体的 FileLogger、DatabaseLogger 等类的存在,
        //      也不知道它们是如何被创建的。实现了高层模块与具体实现的解耦。
    }
}

代码解读:

  • 客户端 (FactoryMethodClient) 想要记录日志,它不直接 new 具体的 Logger,而是选择一个合适的 LoggerFactory(比如 FileLoggerFactory)。
  • 它调用 LoggerFactorywriteLog 方法(或者直接调用 createLogger 获取 Logger 对象)。
  • LoggerFactorywriteLog 方法内部会调用抽象的 createLogger() 方法
  • 由于多态,实际执行的是具体工厂子类(如 FileLoggerFactory重写createLogger() 方法。
  • 这个具体工厂的方法负责 new 出对应的具体产品(如 FileLogger)并返回。
  • 客户端最终通过抽象的 Logger 接口来使用这个被创建出来的具体产品。

五、模式的价值:工厂方法带来的益处

工厂方法模式之所以经典,在于它带来的显著优势:

  1. 优秀的解耦性 (Decoupling):
    • 客户端与具体产品解耦: 客户端代码只依赖于抽象的 Product 和 Creator 接口/类,完全不知道也不关心具体的 ConcreteProduct 类名和创建细节。更换或增加产品类型,客户端代码无需修改。
    • 创建职责分离: 将对象创建的复杂逻辑封装在具体的工厂子类中,使得 Creator 类可以更专注于其核心职责(可能是使用产品进行某些操作)。符合单一职责原则 (SRP)
  2. 强大的扩展性 (Extensibility):
    • 符合开闭原则 (OCP): 当需要引入新的产品类型时,只需要:
      1. 创建一个新的 ConcreteProduct 类。
      2. 创建一个新的 ConcreteCreator 类,实现 createLogger() 方法来返回新产品的实例。
        完全不需要修改现有的 Creator 基类、其他 ConcreteCreator 或客户端代码。系统的扩展性极佳。
  3. 增强代码的灵活性与可维护性 (Flexibility & Maintainability):
    • 子类拥有更大的自主权: 子类可以自由决定如何创建对象,甚至可以返回缓存的实例、进行额外的初始化等。
    • 代码结构更清晰: 创建逻辑被集中到了各个具体的工厂类中,使得代码更容易理解和维护。

六、权衡与考量:工厂方法的“成本”

引入工厂方法模式并非没有代价,需要考虑其可能带来的复杂性:

  • 类爆炸风险 (Class Proliferation): 每增加一个新的产品类型,通常需要同时增加一个新的具体产品类和一个新的具体创建者类。如果产品种类非常多,会导致类层级结构变得庞大和复杂。
  • 增加了抽象层次: 相比直接 new,引入了 Creator 和 Product 的抽象层,增加了代码的间接性和理解成本(虽然这种抽象正是其优势所在)。

因此,如果你的对象创建逻辑非常简单,且未来不太可能变化或扩展,直接 new 可能更直接。但对于需要灵活性、可扩展性、或者希望将创建逻辑与使用逻辑分离的场景,工厂方法模式的优势远大于其引入的少量复杂性。

七、明辨异同:与其他创建模式的对话 (FAQ)

  • Q1: 工厂方法模式 vs 简单工厂模式 (Simple Factory / Static Factory)?

    • A1:
      • 简单工厂: 通常是一个包含静态方法的类,该方法根据传入的参数(如类型字符串)使用 if-elseswitch 来创建并返回不同的产品实例。它不是 GoF 23 种设计模式之一
      • 区别:
        • 结构: 工厂方法是基于继承和多态的(抽象 Creator 和具体 Creator 子类),而简单工厂通常是一个类 + 一个静态方法
        • OCP: 简单工厂在增加新产品时通常需要修改工厂类的静态方法(违反 OCP),而工厂方法通过增加新子类来实现扩展(符合 OCP)。
        • 职责: 工厂方法将创建逻辑分散到各个具体工厂子类中,而简单工厂将所有创建逻辑集中在一个地方。
      • 联系: 简单工厂可以看作是工厂方法的一种简化或初级形态,适用于产品种类不多且不经常变化的简单场景。
  • Q2: 工厂方法模式 vs 抽象工厂模式 (Abstract Factory)?

    • A2: 这是初学者容易混淆的一对。
      • 关注点: 工厂方法关注创建单个产品对象,其目的是让子类决定实例化哪一个类。抽象工厂关注创建一系列相互关联或相互依赖的产品对象(一个产品族),确保这些产品能够协同工作。
      • 结构: 工厂方法通常通过继承(子类重写父类的工厂方法)来实现。抽象工厂通常包含多个工厂方法(每个方法创建一个族中的产品),其实现往往基于对象组合(将一个具体的工厂对象传递给客户端,客户端通过这个工厂对象来创建产品族)。
      • 产品: 工厂方法通常只有一个抽象产品和多个具体产品。抽象工厂有多个抽象产品(构成产品族的各个部分)和对应的多个具体产品。
      • 可以简单记:工厂方法造一个,抽象工厂造一窝。
  • Q3: 工厂方法模式与依赖倒置原则 (DIP) 的关系?

    • A3: 工厂方法模式是实践 DIP 的一个良好范例。客户端代码(高层模块)依赖于抽象的 Creator 和 Product 接口,而具体的 ConcreteProduct 和 ConcreteCreator(低层模块/细节)也依赖于这些抽象。创建的具体细节被封装在 ConcreteCreator 中,高层模块无需关心,实现了依赖关系的“倒置”。

八、心法归纳:授权创建,拥抱变化

工厂方法模式的核心“心法”在于**“授权”与“契约”。它通过在父类中定义一个创建对象的契约**(抽象工厂方法),将实际的创建决策权(实例化哪个具体类)授权给子类。这种“延迟决策”和“职责下放”带来了巨大的好处:

  1. 解耦了使用者与创建者、使用者与具体产品,使得系统更加灵活。
  2. 完美契合了开闭原则,让添加新产品类型变得轻松自然,无需修改现有稳定代码。
  3. 将创建逻辑封装,使得代码结构更清晰,职责更分明。

当你遇到需要根据不同情况创建不同类型对象,并且希望系统能够轻松扩展以支持未来更多类型时,工厂方法模式就是你设计工具箱中一把锋利且优雅的“瑞士军刀”。它鼓励我们面向接口编程,拥抱变化,是构建健壮、可演进软件系统的重要基石。


下一章预告: 《Java 设计模式心法:抽象工厂 (Abstract Factory) - 构建产品家族的蓝图》。如果我们需要创建的不是单个对象,而是一整套相互配套、风格统一的对象(比如一套特定主题的 UI 控件),抽象工厂模式将为我们展现它的威力。敬请期待!

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐