工厂模式横屏:简单工厂 工厂方法 抽象工厂
术语介绍:
工厂 Factory:
工厂不是一个特别清晰的术语,它可以泛指指那些用于生成对象的函数、方法或类。一般情况下,工厂生产类的实例。工厂也可以用于在数据库中生产记录,或在系统中生产文件等。
这些情况下都可以称之为工厂:
- 一个构建GUI的函数/方法;
- 一个创建用户的类;
- 一个调用构造函数的静态函数/方法;
- 一种设计模式。
创建方法/工厂方法/静态工厂方法 Creation Methoed/Factory Method/Static Factory Method:
创建方法(《Refactoring to Patterns》),工厂方法(《Refactoring》),静态工厂方法(《Static Factory Method》)含义相同。工厂方法和工厂方法模式中的工厂方不尽相同,这种叫法容易引起歧义。
在实现中,创建方法即对构造函数的包装,有助于将代码与不同的构造函数的之间进行去耦。创建方法也可能包含特定的逻辑,比如返回现有的对象而不进行新建等。
工厂模式动机:
简单工厂模式、工厂方法模式、抽象工厂模式都属于创建型设计模式,目的是将实例的创建和实例的使用相分离。创建型设计模式允许以不同的方式以动态多态实现消除具体类型的死代码,将类型的确定推迟到程序运行中决定,使程序实现具体类型和程序功能的解耦,对具体类型的确定进行延迟(从编译中延迟的程序运行中)。——封装和延迟。
创建型模式对类的实例化过程进行了抽象,在工厂模式中,表现为将类的创建抽象为工厂类的常见方法、将类的实例使用抽象为对工厂的抽象产品的抽象使用。
三种工厂模式的比较
简单工厂模式:
classDiagram
interface Product
ConcreteProductA .d.|> Product
ConcreteProductB .d.|> Product
ConcreteProductC .d.|> Product
SimpleFactory -u-> Product
定义:
描述:
一个包含基于参数进行一长串判断的创建方法的工厂类,选择生产的产品类并生产返回。简单工厂模式并不符合单一职责原则(创建方法承担了选择判断和创建产品的两重功能),也不符合开闭原则(在程序需要新的功能时需要对创建方法进行修改),一般仅仅在教学中用于引出工厂方法模式和抽象工厂模式。
分析:
- 将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。
- 在调用工厂类的工厂方法时只需要传入一个简单的参数即可,在实际开发中,还可以在调用时将所传入的参数保存在XML等格式的配置文件中,修改参数时无须修改任何源代码。
- 简单工厂模式最大的问题在于工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则相违背。
- 简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。
工厂方法模式:
classDiagram
interface Factory
ConcreteFactoryA .u.|> Factory
ConcreteProductA <.l. ConcreteFactoryA
ConcreteFactoryB .d.|> Factory
ConcreteProductB <.l. ConcreteFactoryB
interface Product
ConcreteProductA .u.|> Product
ConcreteProductB .d.|> Product
描述:
工厂方法模式时简单工厂模式的进一步抽象和推广,解决了简单工厂模式中工厂类的创建方法承担了过多的职责的问题。将具体的简单工厂通过工厂接口独立至各个具体工厂中实现对各种具体产品的生产,并将简单工厂的判断职责推迟至客户端中进行,在客户端中也通过接口延迟了具体工厂类型的决断,使得客户端中的代码变得灵活。
分析:
- 在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”;
- 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销;
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
抽象工厂模式
classDiagram
interface AbstractFactory {
createProductA()
createProductB()
}
interface AbstractProductFamilyA {
use()
}
interface AbstractProductFamilyB {
use()
}
ConcreteFactoryA ..|> AbstractFactory
ConcreteFactoryB ..|> AbstractFactory
AbstractProductFamilyA\nConcreteProductA <.. ConcreteFactoryA
AbstractProductFamilyA\nConcreteProductB <.. ConcreteFactoryB
AbstractProductFamilyB\nConcreteProductA <.. ConcreteFactoryA
AbstractProductFamilyB\nConcreteProductB <.. ConcreteFactoryB
AbstractProductFamilyA\nConcreteProductA .u.|> AbstractProductFamilyA
AbstractProductFamilyA\nConcreteProductB .u.|> AbstractProductFamilyA
AbstractProductFamilyB\nConcreteProductA .u.|> AbstractProductFamilyB
AbstractProductFamilyB\nConcreteProductB .u.|> AbstractProductFamilyB
定义:
提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。
描述:
在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性。一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。
- 产品族:在抽象工厂中, 产品族指同一个具体工厂生产的位于不同产品等级结构的一系列产品;
- 产品等级结构:即产品类的继承结构。
工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、有效率。
分析:
- 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式;
- 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”;
- 在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便;
- 开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)。
总结:
设计模式的抽象程度越高,对依赖倒置原则的吻合度越高,越对接口进行设计而非对具体的类进行设计。在具体类仅有单数个的情况下,接口/抽象类和具体类可以发生多态退化为一个具体类,但应当以接口出发设计程序结构而非从具体类出发进行设计。
接口的使用不仅是对依赖倒置原则的吻合,也可以起到推迟具体类的确定的作用。将具体类的确定从后台推迟至客户端,从客户端推迟至配置文件(依赖反射),可以有效增强代码的灵活性,在程序的要求发生改变时大幅降低修改量。
简单工厂模式-工厂方法模式-抽象工厂模式是抽象程度逐渐增加,逐渐从具体类的组合和包含变为抽象接口的动态多态,类的确定的时机和类的开发的凭据都发生变化。
类起到的封装作用不需要再说。