抽象工厂方法模式——创建型模式(2)
前言
在上一篇介绍的工厂方法模式,主要用于创建单个对象,但是如果需要在一个工厂方法中创建一系列相关或者相互依赖的对象时,我们是又该如何应对呢?如果完全按照工厂方法模式来设计,那么在设计的过程中,我们必然会面对越来越多的工厂类,但是由于这些对象彼此间存在一定的关联依赖性,我们或许可以通过机制将这一系列对象的创建工作统一封装起来?这就是即将登场的——抽象工厂方法模式擅长之处呢!
动机
在软件系统中,我们经常面临着一系列相关或者相互依赖的对象的创建工作,又由于需求的多样化,导致在系统的演化过程中,存在越来越多的系列对象的创建工作。这时,常规的对象创建模式已然不能很灵活地处理这种场景,我们需要提供一种更加紧凑的“封装机制”来规避客户程序与这种多系列对象创建工作的紧耦合。这便是抽象工厂方法模式的由来。
意图
提供一个创建一系列相关或者相互依赖对象的接口,而无需指定它们具体的类。
结构图
- 抽象工厂(Abstract Factory)角色:声明一个创建抽象产品对象的操作接口,是抽象工厂方法模式的核心,与业务逻辑无关。
- 具体工厂(Concrete Factory)角色:创建具体产品对象的操作,含有选择适当的产品对象的逻辑,该逻辑与系统的业务逻辑紧密相关。
- 抽象产品(Abstract Product)角色:为一类产品对象声明的一个接口,是工厂方法所创建的对象的父类。
- 具体产品(Concrete Product)角色:实现了AbstractProduct接口的具体产品,也是一个将被相应的具体工厂创建的产品对象,与业务相关。
代码示例
1: public abstract class AbstractProductA{
2: }
3:
4: public class ProductA1 extends AbstractProductA{
5: public String toString(){
6: return "It is ProductA1";
7: }
8: }
9:
10: public class ProductA2 extends AbstractProductA{
11: public String toString(){
12: return "It is ProductA2";
13: }
14: }
15:
16: public abstract class AbstractProductB{
17: }
18:
19: public class ProductB1 extends AbstractProductB{
20: public String toString(){
21: return "It is ProductB1";
22: }
23: }
24:
25: public class ProductB2 extends AbstractProductB{
26: public String toString(){
27: return "It is ProductB2";
28: }
29: }
30:
31: public abstract class AbstractFactory{
32: public abstract AbstractProductA CreateProductA();
33: public abstract AbstractProductB createProductB();
34: }
35:
36: public class ConcreteFactory1 extends AbstractFactory{
37: public AbstractProductA CreateProductA(){
38: return new ProductA1();
39: }
40: public AbstractProductB createProductB(){
41: return new ProductB1();
42: }
43: }
44:
45: public class ConcreteFactory2 extends AbstractFactory{
46: public AbstractProductA CreateProductA(){
47: return new ProductA2();
48: }
49: public AbstractProductB createProductB(){
50: return new ProductB2();
51: }
52: }
53:
54: public class Client{
55: public static void main(String[] args){
56: AbstractFactory cf1=new ConcreteFactory1();
57: AbstractProductA pA=cf1.CreateProductA();
58: AbstractProductB pB=cf1.createProductB();
59: System.out.println(pA);
60: System.out.println(pB);
61:
62: AbstractFactory cf2=new ConcreteFactory2();
63: AbstractProductA pA2=cf2.CreateProductA();
64: AbstractProductB pB2=cf2.createProductB();
65: System.out.println(pA2);
66: System.out.println(pB2);
67: }
68: }
从上述示例代码中,我们可以清楚地看到,在一个抽象工厂方法里出现了生成两种不同接口对象的方法,而上一篇介绍的工厂方法通常情况下只会出现生成一种接口对象的方法,这是两者最大的区别。之所以在抽象工厂方法里会出现多个生成不同接口对象的方法,主要是在于这若干个不同接口的对象之间存在一定的关联性,换句话说就是它们从逻辑上来说应该属于同一系列下的不同产品。举个比较形象的比喻就是在安装了win8系统的电脑里,通常只会安装与win8系统兼容的软件,因此这些软件就存在一定的关联性,或者说属于win8这个系列下的不同部分(这个比喻可能有点牵强:)),表达的意思差不多就是这样子吧,下面会有更详细的讲解!
现实场景
上文我们已经谈过了抽象工厂方法模式的基本思想和实现过程,为了更清楚地讲述抽象工厂方法模式的“内含”,在讲述现实场景之前,首先让我们来进一步学习加深对抽象工厂方法模式的理解吧。通过引进“产品族”的概念来讲述抽象工厂方法模式,所谓“产品族”指的位于不同产品等级结构,功能相关联的产品组成的家族,如下面所示:
图中一共有四个产品族,分布三个不同的产品结构中。只要指明一个产品所处的产品族以及它所属的等级结构,就可以唯一地确定这个产品。通过产品族的概念来类比抽象工厂方法模式就是一个工厂可以创建出属于不同产品等级结构的一个产品族中的所有对象,如下图所示:
通过上面两个示意图,相信大家对抽象工厂方法模式有了更进一步的理解了吧。说白了,抽象工厂方法模式就是为了创建一系列相关或者相互依赖的对象,而无需指定它们具体的类,个人觉得前半句才是体现其本质的地方,而后半句只是使用接口编程带来的额外效果而已。
前面说了这么多,主要就是为进一步说清抽象工厂方法模式的本质。在现实的软件系统实现中,该模式的使用还是比较常见的。比如,对于游戏软件而言,大多数游戏都有冲关一说,每一关的boss和小兵都不一样,至少攻击力不一样,能量也不一样,它们彼此间的角色关系是不会发生改变的。换句话来说,在游戏中的每一关里,都会出现相应等级、相应能量、相应攻击力的boss和小兵,通常来说,这些角色在每一关特点都会有所不同,抽象到面向对象层面来说,就是这些角色的接口是一样的,但是里面的实现却大不一样,我们需要根据每个关的难易程度来设计实现这些角色。结合我们刚刚所讨论的产品族的概念,就是针对每一关都有相应的一个具体工厂,由其创建每一关所需要的所有角色的具体实现。之所以能够这样实现关键还在于它们角色之间存在一定的关联性,属于同一“产品族”范畴。针对这样一个场景,运用抽象工厂方法模式来实现并不困难,困难的是如何定义好抽象工厂的接口问题以及扩展问题。说到这些方面也就涉及到了抽象工厂方法模式的适应性呢。后面有介绍。
实现要点
- 抽象工厂把产品对象的创建工作延迟到具体工厂的子类来完成
- 没有“多系列对象创建”需求,就没必要考虑使用抽象工厂方法模式。单个对象或者是没有依赖关系的对象的创建工作完全可以使用简单工厂方法或者工厂方法模式来解决,也完全可以胜任。
- 所谓的系列对象指的是彼此间有相互依赖,相互作用的关系。
- 在设计抽象工厂方法接口的过程中,应尽量根据实际需求,将接口定义规范和全面些。因为如果之后再需添加新的各类接口产品,将会不可避免地需要修改抽象工厂方法接口,违背“开——闭”原则。当然,想要做到这点,首先需求必须明确而且符合抽象工厂方法的适合场景。
- 在客户端使用时一般是实例化具体的工厂类,由其完成对属于同一“产品族”的系列对象的创建工作,不同的系列对象的创建工作应该由不由的具体工厂类来完成。
- 可以将工厂单例化,因为同属于一个“产品族”的对象只需要一个具体的工厂实例来创建。因此在实际的设计开发中可以考虑将工厂模式实现成单例模式(单例模式的详细讲解之后给出)。
- 通常抽象工厂方法和工厂方法组合使用来共同应对对象的创建工作。
- 在具体的工厂类中,来用创建具体产品对象的方法其实完全可以通过上一篇的工厂方法来生成对应的具体方法,但是这样做就会产生很多个工厂类,即使这些产品系列之间的差别很小。
运用效果
- 分离了具体的类:Abstract Factory模式帮助你控制一个应用创建的对象的类。因为一个工厂封装了创建产品对象的责任和过程,它将客户与类的实现相分离。客户通过产品的抽象接口来操纵实例。产品的类名也在具体工厂的实现中被分离,它们不会出现丰客户代码中。
- 使得易于交换产品系列:当需要不同产品系列时,只需实例过不同的具体工厂即可产生不同系列的产品对象。
- 有利于产品的一致性。当一个系列中的产品对象被设计成一起工作时,一个应用一次只能使用同一个系列中的对象,而正是抽象工厂方法模式善长之处。
- (硬伤)难以支持新种类的产品:因为抽象工厂接口确定了可以被创建的产品集合,支持新种类的产品就需要扩展该工厂接口,这将涉及AbstractFactory类及其具体子类的改变。
适用性
- 一个系统要独立于它的产品的创建、组合和表示时。
- 一个系统要由多个产品系列中的一个来配置时。
- 当你要强调一系列相关的产品对象的设计以便进行联合使用时。
- 当你提供一个产品类库中,而只想显示它们的接口而不是实现时。
相关模式
- 抽象工厂模式和工厂方法模式:这两个都模式既有区别,又有联系,可以组合使用。工厂方法一般是针对单独对象的创建工作,而抽象工厂模式注重产品族对象的创建,这是两者主要的区别。如果把抽象工厂创建的产品族简化,这个产品族只有一个产品时,那么抽象工厂与工厂方法是差不多呢,也就是抽象工厂退化成工厂方法,而工厂方法也是可以退化成简单工厂方法,这是它们之间的联系。另外一个就是前文刚刚讲述的可能将工厂方法来提供抽象工厂的具体实现,主要体现在对具体产品的创建工作上,将它们两都组合使用。
- 抽象工厂和单例模式:前文也已经表达过,就是通常将具体的工厂方法设计成单例,因为创建属于同一“产品族”的系列对象时,只需要一个具体工厂实例即可。这样就使抽象工厂模式与单例模式组合使用起来。
总结
抽象工厂模式的本质是:选择产品族的实现。定义在抽象工厂里面的方法通常都是有联系的,它们都是产品的某一部分或者是相互依赖的。如果抽象工厂里只有一个产品的创建,那么就退化为工厂方法模式呢。理解了抽象工厂模式,也就理解了OOP的精华:面向接口编程!因为客户在使用抽象工厂来创建具体的产品系列时,是根本不需要知道产品的具体实现细节的,只需要操纵其共有的抽象接口对象即可,实现真正意义上面向接口宗旨!下一篇我们将学习生成器模式!敬请期待!
参考资料:
- 程杰著《大话设计模式》一书
- 陈臣等著《研磨设计模式》一书
- GOF著《设计模式》一书
- Terrylee .Net设计模式系列文章
- 吕震宇老师 设计模式系列文章