# 工厂方法模式

# 概念

工厂方法模式( Factory Method Pattern )是一种创建型设计模式,它定义了一个创建对象的接口(工厂接口),但由子类决定要实例化的类(具体产品类)。该模式将对象的创建逻辑封装在子类中,使得客户端代码可以独立于对象的创建过程。

# 作用

  1. 解耦创建和使用:将对象的创建和使用分离,客户端代码不需要知道对象的创建细节,只需要通过工厂接口获取对象。

  2. 灵活扩展:新增日志记录方式时,只需添加新的日志记录器类和对应的工厂类,无需修改现有代码,符合开闭原则。

  3. 封装创建逻辑:将对象的创建逻辑封装在工厂类中,便于统一管理和维护,减少重复代码。

  4. 支持多态:通过工厂接口可以创建不同类型的对象,客户端代码可以基于多态进行操作,提高代码的可复用性和可维护性。

# 场景

  1. 多态对象创建:多种同类对象创建,支持灵活扩展。

  2. 框架扩展设计:框架预留接口,用户自定义实现。

  3. 动态产品切换:运行时按条件创建不同产品实例。

  4. 复杂构造封装:封装多步初始化或依赖创建逻辑。

# 举例

假设我们有一个日志记录系统,需要支持不同类型的日志记录方式,比如文件日志、数据库日志、控制台日志等。我们可以使用工厂方法模式来设计这个系统。

定义日志记录器接口:

package net.feixiang.creational.factoryMethod;

/**
 * 日志接口
 */
public interface Logger {
    /**
     * 记录日志
     *
     * @param message 日志消息
     */
    void log(String message);
}

实现具体日志记录器类:

package net.feixiang.creational.factoryMethod;

/**
 * 控制台日志类
 */
public class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        // 将日志输出到控制台
        System.out.println("控制台日志: " + message);
    }
}
package net.feixiang.creational.factoryMethod;

/**
 * 数据库日志类
 */
public class DatabaseLogger implements Logger {
    @Override
    public void log(String message) {
        // 将日志写入数据库
        System.out.println("数据库日志: " + message);
    }
}
package net.feixiang.creational.factoryMethod;

/**
 * 文件日志类
 */
public class FileLogger implements Logger {
    @Override
    public void log(String message) {
        // 将日志写入文件
        System.out.println("文件日志: " + message);
    }
}

定义工厂接口:

package net.feixiang.creational.factoryMethod;

/**
 * 日志工厂接口
 */
public interface LoggerFactory {
    /**
     * 创建日志对象
     *
     * @return 日志对象
     */
    Logger createLogger();
}

实现具体工厂类:

package net.feixiang.creational.factoryMethod;

/**
 * 控制台日志工厂类
 */
public class ConsoleLoggerFactory implements LoggerFactory {
    @Override
    public Logger createLogger() {
        return new ConsoleLogger();
    }
}
package net.feixiang.creational.factoryMethod;

/**
 * 数据库日志工厂类
 */
public class DatabaseLoggerFactory implements LoggerFactory {
    @Override
    public Logger createLogger() {
        return new DatabaseLogger();
    }
}

package net.feixiang.creational.factoryMethod;

/**
 * 文件日志工厂类
 */
public class FileLoggerFactory implements LoggerFactory {
    @Override
    public Logger createLogger() {
        return new FileLogger();
    }
}

运行示例:

package net.feixiang.creational.factoryMethod;

/**
 * 工厂方法模式演示
 */
public class FactoryMethodDemo {
    public static void main(String[] args) {
        // 使用控制台日志工厂
        LoggerFactory consoleLoggerFactory = new ConsoleLoggerFactory();
        Logger consoleLogger = consoleLoggerFactory.createLogger();
        consoleLogger.log("这是一个控制台日志消息。");

        // 使用数据库日志工厂
        LoggerFactory databaseLoggerFactory = new DatabaseLoggerFactory();
        Logger databaseLogger = databaseLoggerFactory.createLogger();
        databaseLogger.log("这是一个数据库日志消息。");

        // 使用文件日志工厂
        LoggerFactory fileLoggerFactory = new FileLoggerFactory();
        Logger fileLogger = fileLoggerFactory.createLogger();
        fileLogger.log("这是一个文件日志消息。");
    }
}

控制台输出:

控制台日志: 这是一个控制台日志消息。
数据库日志: 这是一个数据库日志消息。
文件日志: 这是一个文件日志消息。

# 反例

假设我们有一个日志记录系统,需要支持不同类型的日志记录方式,比如文件日志、数据库日志、控制台日志等。如果不使用工厂方法模式,可能会出现以下问题:

定义非工厂方法模式类:

package net.feixiang.creational.factoryMethod.contrary;

/**
 * 非工厂方法模式
 */
public class NonFactoryMethod {
    public static void log(String type, String message) {
        // 后续如有增加类型,这里的代码需修改才能扩展,违反开闭原则,耦合度也高
        if ("console".equals(type)) {
            // 控制台日志记录逻辑
            System.out.println("控制台日志: " + message);
        }else if ("database".equals(type)) {
            // 数据库日志记录逻辑
            System.out.println("数据库日志: " + message);
        } else if ("file".equals(type)) {
            // 文件日志记录逻辑
            System.out.println("文件日志: " + message);
        }  else {
            throw new IllegalArgumentException("无效日志类型: " + type);
        }
    }
}

运行示例:

package net.feixiang.creational.factoryMethod.contrary;

/**
 * 非工厂方法模式演示
 */
public class NonFactoryMethodDemo {
    public static void main(String[] args) {
        NonFactoryMethod.log("console", "这是一个控制台日志消息。");
        NonFactoryMethod.log("database", "这是一个数据库日志消息。");
        NonFactoryMethod.log("file", "这是一个文件日志消息。");
    }
}

控制台输出:

控制台日志: 这是一个控制台日志消息。
数据库日志: 这是一个数据库日志消息。
文件日志: 这是一个文件日志消息。

# 存在的短板


  1. 违反单一职责原则

    Logger 类既负责日志记录逻辑,又负责根据类型创建不同的日志记录器,职责不单一。当需要新增日志记录类型时,需要修改 Logger 类的代码,违反开闭原则。

  2. 扩展性差

    新增日志记录类型时,需要修改 Logger 类的 log 方法,添加新的 if-elseswitch-case 分支。这会导致 Logger 类变得越来越臃肿,难以维护。

  3. 客户端代码与日志记录器耦合度高

    客户端代码需要知道所有日志记录器的类型,并在调用时传入正确的类型字符串。如果类型字符串拼写错误,可能会导致运行时错误。

  4. 难以统一管理日志记录器的创建

    日志记录器的创建逻辑分散在客户端代码中,难以统一管理和维护。例如,如果需要对日志记录器的创建进行额外的配置或初始化操作,需要在多个地方修改代码。

  5. 不利于多态的使用

    这种实现方式下,客户端代码无法基于多态来操作日志记录器,只能通过类型字符串来区分不同的日志记录方式,代码的灵活性和可复用性较低。

相比之下,使用工厂方法模式可以将日志记录器的创建逻辑封装在工厂类中,客户端代码通过工厂接口获取日志记录器对象,解耦了客户端代码与日志记录器的创建过程,提高了代码的可扩展性、可维护性和灵活性。

# 开闭原则

开闭原则( Open-Closed Principle, OCP )是软件设计中的一项核心原则,由伯特兰·梅耶( Bertrand Meyer )提出。该原则指出:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。

# 核心思想


  1. 对扩展开放

    软件实体应该能够通过扩展其功能来适应新的需求或变化,而无需修改其现有的代码。

  2. 对修改关闭

    现有的代码在完成其功能后,应尽量避免被修改,以降低引入错误的风险并保持代码的稳定性。

# 举例


直接实例化对象:

package net.feixiang.creational.factoryMethod.contrary;

import net.feixiang.creational.factoryMethod.Logger;
import net.feixiang.creational.factoryMethod.ConsoleLogger;
import net.feixiang.creational.factoryMethod.DatabaseLogger;
import net.feixiang.creational.factoryMethod.FileLogger;

/**
 * 不符合开闭原则的工厂方法模式示例:直接依赖具体类,增加新日志类型需要修改现有代码。
 */
public class NonOpenClosedPrincipleDemo {
    public static void main(String[] args) {
        // 直接依赖具体类
        Logger logger;
        String type = "console";
        if ("console".equals(type)) {
            // 实例化控制台日志类
            logger = new ConsoleLogger();
        } else if ("database".equals(type)) {
            // 实例化数据库日志类
            logger = new DatabaseLogger();
        } else if ("file".equals(type)) {
            // 实例化文件日志类
            logger = new FileLogger();
        } else {
            throw new IllegalArgumentException("无效日志类型: " + type);
        }
        // 增加其它日志类型需要在这里继续用else if,耦合性高
    }
}

# 解析


  1. 违反开闭原则:新增日志(如 Web )时,必须修改客户端代码中的条件判断逻辑。

  2. 高耦合:客户端直接依赖具体类,难以替换或扩展产品。

  3. 重复代码:若多个地方需要创建对象,相同的条件判断逻辑会重复出现。


总结


工厂方法模式是创建型设计模式,定义创建对象接口,由子类决定实例化类。它能解耦创建与使用,灵活扩展,封装创建逻辑,支持多态。适用于多态对象创建等场景,符合开闭原则,避免直接实例化对象的诸多短板。



微信公众号

QQ交流群
原创网站开发,偏差难以避免。

如若发现错误,诚心感谢反馈。

愿你倾心相念,愿你学有所成。

愿你朝华相顾,愿你前程似锦。