一、工厂模式介绍

在 GOF 的设计模式中,工厂模式有两种工厂方法(Factory Method Design Pattern)抽象工厂(Abstract Factory Design Pattern)

而在 GOF 外还有一种常见的工厂模式,简单工厂模式(Simple Factory Design Pattern)

工厂模式中的工厂和生活中的工厂一样,都是用来生产产品的,在代码中工厂生产的产品是类实例对象。

我们会使用工厂来生产实例对象,而不是自己实例化对象通常有如下几个原因:

1、对象的实例相对复杂。通过工厂抽离实例化代码,使用者无需关注实例化过程简化代码操作,同时让类的职责更单一。
2、对象的实例过程会频繁变更。实例化操作频繁变更会导致使用到该对象实例过程的代码块也需要频繁变更,而使用工厂,能够使得对象的实例化透明化。

二、工厂模式设计模型(使用套路)

模拟一个配置文件加载的场景来介绍工厂模式,场景如下:从配置文件中加载配置,可以是 xml,可以是 properteis 等配置文件,然后通过解析器将配置文件中的配置信息,解析成 BeanDefinition
大体流程如下:

[设计模式]-[创建型]-工厂-介绍 - 图1
实现上述功能主要的三个对象定义:
BeanDefinition : 配置文件解析后映射的对象数据
LoadBeanConfigResource:加载程序,通过解析器解析文件构建 BeanDefinition 对象数据
IBeanConfigParser :解析器接口定义

上述场景加载程序LoadBeanConfigResource)代码主流程简单实现如下:

[设计模式]-[创建型]-工厂-介绍 - 图2

代码主要流程两个步骤:
1、加载配置文件
2、根据配置文件类型构建解析器,进行配置文件解析

本次工厂模式话题围绕配置文件的解析进行展开

2.1、简单工厂

回顾一下起始加载程序代码需要构建一个解析器对象的代码逻辑,如下:

[设计模式]-[创建型]-工厂-介绍 - 图3

上述代码构建解析器对象会遇到两个问题:

1、如果解析器的构建流程很复杂,代码就会变得冗长,构建流程复杂。
2、这里支持 xml 和 properties 文件的解析,如果新增新文件格式,此时就需要修改代码,增加 if-else 分支

这时可以通过简单工厂方法,将解析器的构建过程透明化,加载程序代码无需关注解析器对象的构建,亦或者是新增文件格式导致的解析器判断流程代码变更,加载程序代码直接从解析器工厂中获取解析器,然后直接使用解析器进行解析即可。

进行工厂化,优化后的代码如下:

[设计模式]-[创建型]-工厂-介绍 - 图4

如上述代码所示,直接将 **LoadBeanConfigResource**加载程序
中构建 IBeanConfigParser 解析器的逻辑代码抽离到
一个简单工厂 BeanConfigParserFactory

此时,如果解析器构建的变更,亦或者是新增文件格式的解析,对于LoadBeanConfigResource 加载程序 来说是透明的 ,代码变更只影响简单工厂 BeanConfigParserFactory LoadBeanConfigResource加载程序 无需任何代码变更即可升级最新的操作逻辑。

[设计模式]-[创建型]-工厂-介绍 - 图5

2.2、工厂方法

在上面通过简单工厂,将解析器 IBeanConfigParser 的构建抽离到了工厂中,但是对于简单工厂来说仍然存在问题:就是多种类型的解析器仍然需要使用 if-else 来进行区分,简单工厂 BeanConfigParseFactory 代码如下:

[设计模式]-[创建型]-工厂-介绍 - 图6

简单工厂模式将对象构建抽离了,但是存在 if-else 各种类型解析器构建分支,如果解析器需要解析的文件类型变多,对应的解析器也越多, if-else 分支也会增多,如果对应的解析器构建的流程也变得复杂,这时候就需要对简单工厂代码再进行抽离,将简单工厂中类型的解析器在进行抽离,xml解析器的构建交给xml解析器工厂来完成,
properties 解析器的构建交给properties解析器工厂来完成,各种文件格式解析器的构建交给各自对应的工厂来完成,这就演变出了 **工厂方法模式**,相关逻辑图如下:

[设计模式]-[创建型]-工厂-介绍 - 图7
代码优化为方法工厂,相关代码实现如下:

[设计模式]-[创建型]-工厂-介绍 - 图8

如上述代码所示,将不同文件解析的解析器构建细化到各自的工厂类构建,如:xml解析器的交由xml解析器工厂XmlConfigParseFacotry来构建。

同时简单工厂中,获取解析器不再使用 if-else ,同时也无需自己构建,只需根据文件格式,从相应的解析器工厂获取解析器即可。

2.3、抽象工厂

上面的简单工厂和工厂方法对应的解析器都是文件解析器,假设此时新增注解的方式进行解析,虽然都是解析数据,但是文件解析和注解解析模式完全不同。
这两种类型的解析属于同种类型的解析,但是解析方式又不同,无法定义统一的接口规范,如果在搞一套 工厂方法来实现,则代码逻辑图如下

[设计模式]-[创建型]-工厂-介绍 - 图9
如果使用上述方式来实现,又会出现问题,就是如果又多了一种新的解析方式,又需要使用 if-else 来区分那种类型的解析,又会回到原点,回到如何去除 if-else, 直接把整套逻辑搞复杂,这时候就可以尝试使用 抽象工厂 来解决。

如果使用 抽象工厂 的实现,可以理解为,就是把 工厂方法 中的 简单工厂 替换掉,实现的逻辑图如下:

[设计模式]-[创建型]-工厂-介绍 - 图10

根据上图对应的代码实现如下:

[设计模式]-[创建型]-工厂-介绍 - 图11

上述代码,可以这样理解,抽象工厂本质上就是用来组合多个具有相同类型功能工厂的工厂。

三、总结对比

以上述配置文件解析案例进行总结

3.1、不使用工厂模式的设计逻辑图

**[设计模式]-[创建型]-工厂-介绍 - 图12
复杂对象的构建由用户负责,包括应对解析器对象的构建逻辑变更,以及新增文件格式解析时操作流程的变更。

3.2、简单工厂的设计逻辑图

[设计模式]-[创建型]-工厂-介绍 - 图13
将对象的构建,不同文件格式不同解析器的构建等操作交给
简单工厂**来完成,解析的构建对于使用者来说完全透明,代码的变更,使用者无需关心。

3.3、工厂方法设计逻辑图

[设计模式]-[创建型]-工厂-介绍 - 图14
随着解析器构建,以及文件格式的增加,简单工厂变得复杂,就需要再一次进行优化抽离,将每种解析器对象的构建都交由各自的工厂完成,原本的简单工厂只需提供根据文件格式选择从相应的解析器工厂方法中获取解析器对象即可。

3.4、抽象工厂设计逻辑图

[设计模式]-[创建型]-工厂-介绍 - 图15
都是解析器,但是文件解析器和注解解析器,两者无法归类到同一类型的工厂生产,但是他们又具有相同的功能,这时候就可以使用抽象工厂,关联两者的工厂方法,对外同时提供两种类型的解析器。


【公众号】花好夜猿
wxlogo.jpg