本文中涉及到的完整代码存放于以下 GitHub 仓库中 LearningCode

1. 理论部分

工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,但是让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。
工厂方法模式简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。

1.1 结构与实现

工厂方法模式包含以下 4 个角色:

  • Product(抽象产品)
    • 职责:定义工厂所创建对象的接口。
    • 实现:Product 通常声明为抽象类或接口。
  • ConcreteProduct(具体产品)
    • 职责:实现了 Product 中的抽象接口。
    • 实现:ConcreteProduct 声明为类。
  • Factory(抽象工厂)
    • 职责:声明工厂方法,用于返回一个产品。
    • 实现:Factory 可以声明为抽象类、接口或类。Factory 中定义的工厂方法其参数列表可以包含类型控制参数和业务参数,并且可以被重载,以满足产品对象的多样化创建需求。
  • ConcreteFactory(具体工厂)
    • 职责:实现 Factory 中的接口。
    • 实现:ConcreteFactory 声明为类。

下面介绍 Factory 的具体实现细节:

  • 抽象工厂类存在以下 3 种设计:
    • 抽象工厂类被设计为抽象类,且不提供它所声明的工厂方法实现。
    • 抽象工厂类被设计为抽象类,但提供它所声明的工厂方法的默认实现。
    • 抽象工厂类被设计为类,且提供它所声明的工厂方法的默认实现。
  • 抽象工厂类的工厂方法存在以下 2 种设计:
    • 工厂方法不包含类型控制参数(例如:用于指定工厂的字符串,工厂的类全限定名等),适用于每个产品的创建过程较为复杂,且创建逻辑具有显著差异的场景。
    • 工厂方法包含类型控制参数,此类工厂方法也称参数化工厂方法,适用于每个产品的创建过程比较简单的场景。
      • 要点:该设计方案通常会提供一个默认的实现类,用于创建绝大多数产品;对于少数需要定制创建逻辑的产品,可以通过继承默认实现类并重写工厂方法来实现。在继承默认实现类时,需要调用父类的工厂方法,以将除少数产品以为所有产品的创建职责委托给父类。
      • 优点:可以有效预防工厂方法模式中子类爆炸的问题。

工厂方法模式的 UML 类图如下所示:
在这里插入图片描述

1.2 扩展

下面介绍工厂方法模式的 2 种扩展:

  • 延迟初始化:一个对象被消费完毕后,并不立即释放,工厂类保持其初始状态,等待再次被使用。
  • 隐藏工厂方法:为了进一步简化客户端的使用,还可以对客户端隐藏工厂方法,此时在工厂类中直接调用产品类的业务方法,客户端无须调用工厂方法创建产品对象,直接使用工厂对象即可调用所创建的产品对象中的业务方法。

1.3 优缺点与适用场景

工厂方法模式具有以下优点:

  • 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
  • 使用工厂方法模式的另一个优点是在系统中加入新产品时无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品即可,这样系统的可扩展性也就变得非常好,完全符合开闭原则。

工厂方法模式存在以下缺点:在添加新产品时需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。

工厂方法模式适用于以下场景:

  • 当一个类不知道它所必须创建的对象的类时。
  • 当一个类希望由它的子类来指定它所创建的对象时。
  • 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。

2. 实现部分

以 Java 代码为例,演示以下 3 种工厂方法模式的实现:

  • 工厂方法不包含类型控制参数
  • 工厂方法包含类型控制参数
    • 简单字符串、枚举
    • 类限定名

工厂方法模式的扩展实现比较简单,这里不再演示

案例介绍:
在这里插入图片描述

2.1 工厂方法不包含类型控制参数

抽象产品 Logger 的代码如下:

public interface Logger {

    void writeLog();
}

抽象产品工厂 LoggerFactory 的代码如下:

public interface LoggerFactory {
    Logger createLogger();
}

以文件日志记录器为例,FileLogger的代码为:

public class FileLogger implements Logger{
    @Override
    public void writeLog() {
        System.out.println("文件日志记录");
    }
}

FileLoggerFactory的代码为:

public class FileLoggerFactory implements LoggerFactory{
    @Override
    public Logger createLogger() {
        Logger logger = new FileLogger();
        // 省略初始化文件日志记录器的代码
        return logger;
    }
}

客户端调用:

public class Main {
    public static void main(String[] args) {
        // 可以使用环境变量来优化客户端调用
        LoggerFactory factory = new FileLoggerFactory();
        Logger logger = factory.createLogger();
        logger.writeLog();
    }
}

可以发现,若要新增日志记录器,必须创建新的日志记录器类,和日志记录器工厂类,即每一个产品类都要对应一个产品工厂,容易出现子类爆炸的问题。容易造成子类爆炸的问题。
完整的 UML 类图如下所示:
在这里插入图片描述

2.2 工厂方法包含类型控制参数:方式一

使用类型控制参数对上述代码进行优化,这里对 LoggerFactory 中的方法进行了重载,使用枚举和字符串两种类型来获取日志记录器。
LoggerFactory 中的代码:

public interface LoggerFactory {
    Logger createLogger(String type);
    Logger createLogger(LogType logType);
}

创建一个DefaultLoggerFactory,用来处理绝大多数日志记录器的创建。

public class DefaultLogFactory implements LoggerFactory{
    @Override
    public Logger createLogger(String type) {
        if ("database".equalsIgnoreCase(type)) {
            // 省略初始化数据库日志记录器的代码
            return new DatabaseLogger();
        }else if ("file".equalsIgnoreCase(type)) {
            // 省略初始化文件日志记录器的代码
            return new FileLogger();
        }else {
            throw new IllegalArgumentException("Unsupported logger type: " + type);
        }
    }

    @Override
    public Logger createLogger(LogType logType) {
        return switch (logType) {
            case DATABASE -> {
                // 省略初始化数据库日志记录器的代码
                yield new DatabaseLogger();
            }
            case FILE -> {
                // 省略初始化文件日志记录器的代码
                yield new FileLogger();
            }
        };
    }
}

客户端调用:

public class Main {
    public static void main(String[] args) {
        // 可以使用环境变量来优化客户端调用
        LoggerFactory factory = new DefaultLogFactory();

        Logger logger = factory.createLogger("file");
        logger.writeLog();
    }
}

可以看出,若 DefaultLoggerFactory 能够承担绝大多数的日志记录器的创建,那么可以有效预防子类爆炸的问题。此时若需要新增 RedisLogger,又不能修改 DefaultLoggerFactory 中的代码,只能选择继承DefaultLoggerFactory 自定义创建 RedisLogger的逻辑。

public class RedisLogFactory extends DefaultLogFactory{
    @Override
    public Logger createLogger(String type) {
        if ("redis".equalsIgnoreCase(type)) {
            return new RedisLogger();
        }
        return super.createLogger(type);
    }
}

可以看出,若 DefaultLogFactory 一开始不能够尽可能多的容纳不同日志记录器的逻辑,仍然会出现子类爆炸的问题。同时,若类型控制参数使用枚举,也会违反开闭原则。因为这意味着需要修改LogType中的代码,而字符串则不需要。
完整 UML 类图如下所示:
在这里插入图片描述

2.3 工厂方法包含类型控制参数:方式二

使用类限定名作为类型控制参数,此时 DefaultLoggerFactory 中的代码为

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class DefaultLogFactory implements LoggerFactory {

    @Override
    public Logger createLogger(Class<? extends Logger> clazz) {
        try {
            Constructor<? extends Logger> constructor = clazz.getConstructor();
            constructor.setAccessible(true);
            return constructor.newInstance();
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
}

此时,若日志记录器的创建都可以基于构造函数完成,则新的日志记录器的出现无须添加新的LoggerFactory,可以有效避免子类爆炸的问题。
完整的 UML 图如下所示:
在这里插入图片描述

2.4 总结

演示以下 3 种工厂方法模式的实现:

  • 工厂方法不包含类型控制参数
    • 优点:实现简单。
    • 缺点:每一个产品类都要对应一个产品工厂,容易出现子类爆炸的问题。
  • 工厂方法包含类型控制参数
    • 简单字符串、枚举
      • 优点:实现简单,可以有效减少子类的数量。
      • 缺点:在默认实现类中限定了所有可能的产品,如果要添加新的产品,需要通过继承默认实现类或抽象工厂类编写独立创建逻辑。
    • 类限定名
      • 优点:可以有效减少子类的数量,不用在默认实现类中限定所有可能的产品。
      • 缺点:实现复杂,且只能用于创建逻辑简单的场景;一旦添加了需要复杂创建逻辑的产品,仍需要通过继承默认实现类或抽象工厂类编写独立创建逻辑。

3. 参考资料

  1. 设计模式快速入门 —— 图灵星球TuringPlanet —— 工厂方法模式
  2. Java设计模式详解 —— 黑马程序员 —— 工厂方法模式(P38 ~ P40)
  3. Java设计模式 —— 尚硅谷 —— 单例模式(P43 ~ P44)

学习读物:

  1. 《设计模式:可复用面向对象软件的基础》—— Erich Gamma 著 —— 李英军 译 —— 第 3.3 节(P81)
  2. 《Java 设计模式》 —— 刘伟 著 —— 第 4 章(P42)
  3. 《设计模式之美》—— 王争 著 —— 第 6.3 节(P180)
  4. 《设计模式之禅》 —— 第 2 版 —— 秦小波 著 —— 第 8 章(P65)
  5. 《图解设计模式》—— 结城浩 著 —— 杨文轩 译 —— 第 4 章(P33)

电子文献:

  1. 设计模式教程 —— 菜鸟教程 —— 工厂方法模式
  2. 99+ 种软件模式 —— long2ge —— 工厂方法模式
Logo

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

更多推荐