《Java Design Patterns》第二章 工厂方法模式

第二章 工厂方法模式

工厂方法模式又称为工厂模式、虚拟构造模式或多态工厂模式,是一种类创建型模式。

一、设计一个日志记录器

设计一个日志记录器,可以通过多种途径保存系统的运行日志,如文件记录或数据库记录等。

1.1 简单模式实现

// 抽象产品类
interface Logger{
    void writeLog();
}
// 具体产品类
class FileLogger implements Logger{
    @Override
    void writeLog(){
        System.out.println("文件日志记录");
    }
}
class DatabaseLogger implements Logger{
    @Override
    void writeLog(){
        System.out.println("数据库日志记录");
    }
}
// 工厂类
class LoggerFactory{
    public static Logger createLogger(String type){
        switch(type){
            case "file":
                // 创建文件、初始化文件日志、等其他操作
                return new FileLogger();
            case "db":
                // 连接数据库、创建数据库日志、等其他操作
                return new DatabaseLogger();
            default:
                throw new UnSupportOperationException();
        }
    }
}

1.2 缺陷与问题

虽然简单工厂模式实现了对象的创建和使用分离,但是仍然存在以下问题:

  1. 工厂类过于庞大,包含了许多的条件判断,导致维护和测试难度增大
  2. 系统扩展不灵活,如果新增其他类型的日志记录器,必须修改静态工厂方法的业务逻辑,违反“开闭原则”

1.3 解决思路

简单工厂方法的缺陷在于违反了“开闭原则”,每次新增新类型时必须修改静态工厂方法的业务逻辑。所有的产品都由一个工厂来创建,工厂职责较重,业务逻辑较为复杂,具体产品和工厂类之间耦合度高。严重影响了系统的灵活性和扩展性。

通过引入工厂方法模式,将工厂与产品拆分,降低耦合,提高系统灵活性。

二、工厂方法模式

2.1 概述

在工厂方法模式中,不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构。

工厂方法模式:定义一个用于创建对象的接口,让子类决定将哪一个类实例化。这样可以将类的实例化延迟到其子类。

2.2 工厂方法模式中的角色

  1. Product(抽象产品):产品对象的公共父类
  2. ConcreteProduct(具体产品):实现抽象产品接口,由具体工厂创建,一一对应
  3. Factory(抽象工厂):在抽象工厂中,声明了工厂方法,用于返回一个产品。
    1. 抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口
  4. ConcreteFactory(具体工厂):抽象工厂的子类,实现了抽象工厂中定义的方法,通过调用工厂方法,返回一个具体产品类的实例。

工厂方法模式与简单方法模式的区别就在于,多了一个抽象工厂类。

在抽象工厂中声明工厂方法,有具体工厂类来创建子类,不同的具体工厂可以创建不同的具体产品。

在实际使用中,还可以在具体工厂中做一些初始化工作,如资源加载、环境配置等工作。

而客户端只需针对抽象工厂编程即可。

三、解决方案

// 抽象产品类
interface Logger{
    void writeLog();
}
// 具体产品类
class FileLogger implements Logger{
    @Override
    void writeLog(){
        System.out.println("文件日志记录");
    }
}
class DatabaseLogger implements Logger{
    @Override
    void writeLog(){
        System.out.println("数据库日志记录");
    }
}
// 抽象工厂类
interfact Factory{
    Logger createLogger();
}
// 具体工厂类
class FileLoggerFactory implement Factory {
    @Override
    public Logger createLogger(){
        // 初始化环境等
        return  new FileLogger();
    }
}
class DatabaseLoggerFactory implement Factory {
    @Override
    public Logger createLogger(){
        // 初始化环境等
        return  new DatabaseLogger();
    }
}
// 测试
public static void main(String[] args){
    Factory factory = new FileLoggerFactory();
    Logger logger = factory.createLogger();
    logger.writeLog();
}

四、扩展

使用 反射与配置文件 扩展系统。

为了让系统拥有更好的灵活性和可扩展性,在客户端代码汇总不使用new关键字来创建工厂对象,而是将具体工厂类的类名存储在配置文件中,通过读取配置文件获取类名字符串,再使用 Java反射机制,根据类名字符串来生成对象。

五、隐藏工厂方法

为了进一步简化客户端的使用,还可以对客户端隐藏工厂方法,在抽象工厂中直接调用产品类的业务方法。

abstract class Factory{
    public void writeLog(){
        Logger logger = this.createLogger();
        logger.writeLog();
    }
    abstract Logger createLogger();
}

这样,客户端直接调用抽象工厂的方法即可调用具体的产品业务方法。

六、工厂方法模式总结

优点

  1. 由工厂方法来创建客户所需的产品,以及隐藏了具体产品类的实例化过程,客户只需关心所需产品对应的工厂,无需关系创建细节,甚至无需知道产品类的类名。
  2. 基于工厂角色和产品角色的多态性设计是工厂方法的关键。能让工厂可以自主确定创建何种产品对象,而创建对象的细节封装在具体工厂的内部。
  3. 符合“开闭原则”,向系统中新增产品时,无需修改抽象工厂和抽象产品提供的接口,无需修改客户端,只需要添加一个具体工厂和具体产品即可。

缺点

  1. 在添加新产品时,需要编写新的具体产品类,还需要提供对应的具体工厂类。系统中类的个数增加,提升了系统复杂度,更多的类需要编译和运行,会给系统带来一些额外开销。
  2. 由于考虑系统的可扩展性,需要引入抽象层,增加了系统抽象性和理解难度。且实现时可能用到DOM、反射等技术,增加系统实现难度

适用场景

  1. 客户端不知道具体所需的对象,只知道工厂即可。
  2. 抽象工厂类通过其子类来指定创建哪个对象。
    1. 抽象工厂类只需要提供一个创建工厂的接口,由其子类-具体工厂类来决定要创建的对象
    2. 利用面向对象的多态性和里氏替换原则,在程序运行时,由子类替换父类,从而使系统更容易扩展

七、练习

使用工厂方法模式设计一个程序来读取各种不同类型的图片格式,针对每一种图片格式都设计一个图片读取器,如GIF图片读取器用于读取GIF格式的图片、JPG图片读取器用于读取JPG格式的图片。需充分考虑系统的灵活性和可扩展性。

文章作者: koral
文章链接: http://luokaiii.github.io/2019/06/26/读书笔记/《JavaDesignPatterns》/4.工厂方法模式/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自