Design-Pattern-Factory-Pattern
接下来就是介绍具体的比较经典的设计模式。
按照常规的分类方式,设计模式分为创建型模式、结构型模式和行为型模式。创建型模式关注对象的创建,结构型模式关注类/对象之间的关系,行为型模式关注对象之间的交互。
实际上,结构型模式和行为模式的边界很模糊。不同对象/类之间的关系限制了之间的交互模式,交互模式又依赖于对象之间的关系。总之,结构型模式关注系统的静态结构,用于构建对象和类之间的关系,而行为型模式关注系统的动态行为,用于管理对象之间的交互以实现特定行为。
本系列首先从创建型模式开始介绍,这是相对简单,使用频率更高的设计模式。创建型模式包含工厂方法模式、抽象工厂模式、生成器模式、原型模式、单例模式。
工厂模式
通常来说,工厂模式包括简单工厂(不被包含在许多设计模式教材中)、工厂方法、抽象工厂。在一些书籍中可能还会包含其他模式,本篇只介绍这三种模式。
归根结底,工厂模式是一种创建型模式,其根本目的是创建对象。在开发中,最常见的新建对象的方式是通过new
关键字,后面跟着构造函数的调用,使用一个目标类型的变量来接受新建对象的引用。
1 |
|
如果使用默认构造函数(无其他明确定义的构造函数,自动生成的无参构造函数),生成的对象属性默认初始化为null
,通常需要在后续代码中调用对象的setter
给对象赋值。新建对象,并对其属性赋值的过程统称为创建对象。
如果在代码中,多处地方都使用了此类new
之后.setX()
的代码来创建对象,并且代码有非常多重复的地方,便可以新建一个类专门负责这类对象创建的过程,这个类就是工厂类。这样做的好处是,如果这个类发生变动,例如新增/删除了一个属性,则不需要去追溯所有创建此类对象的地方,而只需在一个统一入口中进行调整,以便于代码维护。这也符合单一职责原则,即使用这些对象的地方不该拥有创建对象的职责,否则代码会变得更耦合。
额外思考一下,是否一定需要新建一个类来负责对象创建过程?能否把这个创建过程写在类的定义中,例如类的构造函数或静态方法中,是否也能实现创建对象统一入口的特性?笔者认为这是可行的。但当对象创建过程复杂时,例如需要查配置文件、查数据库表、访问网络时,把这些实现写在一个需要在多处使用的Java Bean
中,这个Bean
会添加很多依赖,这不符合模块化编程的思想,也增加了创建过程和使用过程的耦合。而静态方法中没法依赖非静态的属性,写法上会多很多限制。并且当涉及代码扩展时,会希望有一个统一的入口去创建这些有些相似但不同的类,而不是分布在每个类的构造函数中。
所以当类简单且不涉及扩展时,使用构造函数或静态方法来创建对象是可行的;但涉及创建过程复杂和类的拓展性时,工厂模式是更为合适的实践。
当涉及类的拓展时,为了保证代码的通用性,通常会使用接口来表示一系列具有类似功能的类。当工厂类中创建对象方法,返回的不是某个特定类的对象,而是一系列实现某个对象的接口,那么认为这就是工厂模式。此时在实际使用这个对象的地方,可以使用接口类型作为变量类型。如此这样,当替换/拓展了具体对象的类型,只要是实现了同一个接口,在使用这个对象的地方(例如使用变量接收创建的对象,调用对象的方法等,通常称为客户端
)不需要改变代码就能正常运行,代码具有良好的拓展性。
1 |
|
简单工厂
上文描述的场景,基本上就是简单工厂的核心了。为了总结简单工厂的特征,对上文中提到的一些概念进行命名。
抽象产品接口:为了描述一系列具有相似功能的具体产品类定义的接口,相似部分通过声明接口方法来表示。在客户端中使用产品接口,可以不改变客户端代码,即可实现功能的拓展,符合开闭原则。
具体产品类:产品接口的具体实现类。需要进行扩展时,可以定义一个新的具体产品,实现产品接口,并在工厂类的创建方法中新增对应的实例化此类的代码。
工厂类: 包含了创建产品对象的方法,返回一个产品接口类型的对象,根据客户端的需求来创建具体产品类对象
客户端:调用工厂类方法来创建对象,并使用产品接口作为变量类型接收返回的对象
1 |
|
对于简单工厂,“简单”二字体现在工厂类中,通常使用一个静态公共方法,并通过接受一个入参,根据入参来决定具体创建哪个具体产品类。这在代码中通常由一系列的if else
或switch
语句来实现。如果创建对象过程复杂,需要依赖其他对象,静态方法可能无法满足,那么写成一般方法,并在客户端中完成工厂类的实例化,再来创建对象。
1 |
|
可能这样描述还无法感受简单在哪。在后文介绍工厂方法和抽象工厂时,对比不同工厂模式的工厂类怎么处理,会有更直观的感受。
对于简单工厂来说,如果需要进行拓展,需要:
- 新建一个具体产品类并实现产品接口
- 工厂类中的创建方法中新增一个
if else
分支,用于返回新建的具体产品类对象 - 客户端中调整调用创建方法时的入参
工厂方法
在简单方法中,所有具体产品类的创建方法都在同一个工厂类中,因此拓展新具体产品时,不可避免地会对已有代码进行修改,违反了开闭原则。而在客户端中,由于只存在一个工厂类,不需要利用多态的特性,所以直接调用工厂类的静态方法。
要避免扩展时对已有代码的修改,这意味着工厂的创建方法也需要被拓展,工厂类本身也需要抽象。
因此相对于简单工厂,工厂方法对工厂类进行了抽象,新增以下定义
- 抽象工厂接口/抽象类:声明了创建产品接口的方法。如果除创建产品接口方法外,多个具体工厂类有许多类似代码,可以把这些代码抽取到工厂抽象类中(通常认为接口不提供方法实现),提供这些公共方法的默认实现。抽象类的特性也限制了其无法被实例化,并且子类必须实现声明的抽象方法。某种程度上可以理解为提供默认方法的接口。
- 具体工厂类:具体工厂是抽象工厂的实现类/子类,实现了抽象工厂中定义的工厂方法
与简单工厂不同,工厂方法通过抽象工厂来创建对象。这意味着客户端使用的是抽象工厂,而不是具体工厂类。相对于简单工厂中调用工厂类的静态方法,进一步降低了代码耦合。
抽象工厂这个词可能会产生歧义。前文提到“工厂模式包括简单工厂、工厂方法、抽象工厂”中的抽象工厂是指抽象工厂设计模式,而在此处定义的抽象工厂是接口/抽象类,是工厂方法模式中类组织关系中的一个角色。至于为什么工厂方法模式包含抽象工厂的概念,还要叫工厂方法,就不是本文要讨论的。设计模式的命名沿用了四人帮的著作《设计模式:可复用面向对象软件的基础》。

1 |
|
1 |
|
1 |
|
从上述代码来看,由于通过new CreaterClassA()
实例化具体工厂类,客户端仍含有对具体工厂类的依赖。但可以定义成员变量,并通过IoC
注入,那么在客户端中将不会有关于任何CreaterClassA
的依赖,降低了代码的耦合度。
对于工厂方法来说,如果需要进行拓展,需要:
- 新建一个具体产品类并实现产品接口,这和简单工厂是一样的
- 需要新增一个具体工厂类,并实现抽象工厂接口的创建对象方法
- 在客户端中实例化具体工厂类,如果是通过依赖注入则不需要改动客户端
抽象工厂
在工厂方法模式中,抽象工厂定义了一个创建某种抽象产品的方法。在实际开发场景中,某个抽象产品可能并不是单独使用,还需要与其他有关联的抽象产品一起使用。例如在GUI相关代码中,按钮是一个抽象产品,文本框也是一个抽象产品。对于按钮这个抽象产品来说,有对应的Windows按钮具体产品实现,也有Mac按钮具体产品实现。文本框同理。对于程序来说,绘制GUI需要多种组件。但在一个应用程序中,创建这些组件对象,使用的都是同一个平台的具体产品实现。几乎不出现使用Windows的按钮,但使用Mac的文本框这种情况。这意味着具体产品可以通过平台维度进行分类。
平台 | 按钮抽象产品 | 文本框抽象产品 |
---|---|---|
Windows | Windows按钮具体产品 | Windows文本框具体产品 |
Mac | Mac按钮具体产品 | Mac文本框具体产品 |
如果沿用工厂方法模式,需要创建按钮抽象工厂接口、文本框抽象工厂接口、以及Windows按钮具体工厂类、Mac按钮具体工厂类、Windows文本框具体工厂类、Mac文本框具体工厂类。这是只有两种平台、两种抽象产品的情况下,当产品和平台数量变多时,具体工厂类的数量会急剧增加。
对于产品来说,每个产品都要有对应的代码,具体产品类的数量是无法减少的。而对于具体工厂类来说,由于某些产品会一起使用,而某些产品不可能一起使用。可以把能一起使用的抽象产品的创建都定义在同一个抽象工厂中。如此一来,抽象工厂不是只创建一种抽象产品,而是多种相关联的抽象产品。具体工厂类的数量可以从抽象产品种类 * 平台种类
压缩至
平台种类
。在客户端调用抽象工厂的方法也从创建一种产品,变成创建多种相关的产品。

对于抽象工厂来说,如果需要进行拓展,通常是新增一种平台,需要:
- 实现所有抽象产品的具体产品类
- 新增一种具体工厂类,实现所有抽象产品的创建方法,返回1中新增的具体产品类
总的来说,抽象工厂模式,适用于存在多种抽象产品相互关联,可以通过某种维度对具体产品类进行分类的场景(上文中的操作系统平台)。在抽象工厂中,声明创建一系列抽象产品的创建方法,并在每个维度的具体工厂中实现这些方法。
总结
介绍了工厂模式,工厂模式包括简单工厂、工厂方法、抽象工厂。
在工厂模式中,几个重要的概念为:抽象产品、具体产品、抽象工厂、具体工厂、客户端。
在简单工厂中,不存在抽象工厂,直接在具体工厂中提供了根据入参创建多种具体产品对象的创建方法,以抽象产品类型返回。这个方法通常是静态方法。客户端通过依赖具体工厂类来创建对象。
在工厂方法中,抽象工厂中定义了创建一种抽象产品的方法,需要在具体工厂中实现。客户端通过抽象工厂,调用创建方法来创建一种抽象产品对象。
在抽象方法中,抽象工厂中定义了一系列创建相关联的抽象产品的方法,一个具体工厂需要实现所有抽象产品的创建方法。客户端通过抽象工厂,调用创建方法来创建一系列抽象产品对象。