前面陆陆续续的更新了三篇关于设计模式的博客,是关于“策略模式”、“观察者模式”、“装饰者模式”的,今天这篇博客就从“兵工厂”中来探索一下“工厂模式”(Factory Pattern)。“工厂模式”又可以分为“简单工厂模式”(Simple Factory Pattern)、“工厂方法模式”(Factory Method Pattern)和“抽象工厂模式”(Abstract Factory Pattern)。今天这篇博客就从头到尾的来介绍一下这三种模式,并且给出相应的Swift代码的实现。在文章的最后会给出“工厂方法模式”和“抽象工厂模式”的使用场景,并在一个示例中将两者结合起来使用。同样,博客最后仍然会给出示例在github上的分享链接。
工厂模式,顾名思义,肯定和工厂有关的模式。在现实生活中,我们都知道工厂是赋值生成产品的,也就是说工厂往外输出不同的产品,这些产品可以是同一类型,也可以是不同的类型。在我们设计模式中的工厂也是用来生产产品的,不过此产品非比产品。工厂模式中的工厂负责生产“对象”,该工厂也就是对象的工厂。我们在使用工厂模式时,需要使用哪种类型的对象,我们就告诉“工厂”,工厂就会根据我们的指令来生产出相应类型的对象。
工厂模式的应用场景大部分是当你根据不同类型来生成不同对象时,就可以使用工厂模式来解决。也就是说将创建对象的过程封装到工厂中来完成。接下来我们将会通过兵工厂造武器的例子来好好的聊一下工厂模式。这个兵工厂的示例我们先不使用工厂模式来实现出来,然后在通过“简单工厂”、“工厂方法”以及“抽象工厂”模式来实现出来。当然下方我们还会用到“装饰者模式”,关于装饰者模式的详情请参见《“花瓶+鲜花”中的装饰模式(Decorator Pattern)》。废话少说,进入今天的主题。
一、创建无"兵工厂"的武器库
这一部分我们先给出不使用任何类型的“工厂模式”来实现我们的武器库。该示例与之前我们介绍《“穿越火线”中的“策略模式”(Strategy Pattern)》使用的示例是一直的,不过之前我们侧重于“策略模式”,而如今我们注重于“工厂模式”。当然这两者可以共存,不过今天我们的重点是“工厂模式”,所以就不对“策略模式”进行讨论了。下方是我们将要实现示例的类图。
从下方的类图不难看出,WeaponType是武器类型的接口(协议),其中有一个必须实现的方法就是fire()方法。AK、AWP、HK都实现了WeaponType协议。WeaponUser则是武器的使用者,WeaponUser与WeaponType当然是依赖关系。下方就是没有使用“工厂方法”的类图。
从上面的类图来看,实现我们的代码是并不困难的。下方就是WeaponType协议与武器AK、AWP、HK的具体实现。在每种武器实现时都有一个fire()方法,每种武器的fire()都有其独特之处。
实现完各种武器后,我们得创建一个武器的使用者WeaponUser。武器的使用者根据武器的类型来使用武器。下方是武器的使用者WeaponUser、武器枚举WeaponTypeEnumeration、测试用例以及输出结果的截图。WeaponUser中的fireWithType()方法就是根据不同的武器类型来创建不同的武器对象然后在调用武器的fire()方法。下方WeaponUser是直接对武器进行的创建,未用到工厂模式。
二、“简单工厂”(Simple Factory)
由易到难,紧接着我们要使用“简单工厂”模式对上述进行重写。那么我们为什么要使用“简单工厂”模式进行重写呢?因为在之前我们“重构”的系列博客中也不止一次的提到过,要将变化的部分与不变的部分进行分离,对变化进行封装。在上面的示例中,WeaponUser中的Swift-Case语句不难看出,随着武器种类的增加或者减少,这个Switch语句就会随之改变,后期会对此进行修改,可是WeaponUser中的其他部分是不变的。
本着将变化进行封装的原则,我们将上面的Switch语句进行封装,将其放到一个新的类中,在我们的WeaponUser中使用这个新封装的类来创建不同的武器。对上述示例变化的内容提取封装,于是就是形成了我们的“简单工厂模式”。这个新提取被封装的新的创建武器的类就是我们的工厂类,其职责就是用来创建各种各样的武器,这个简单工厂就是我们的“兵工厂”。下方就是我们“简单工厂”的类图,与第一部分的类图相比,不难发现在WeaponUser与WeaponType之间多了一个WeaponFactory,这个WeaponFactory就是我们的简单工厂。类图如下:
有上面的“类图”可以给出其代码实现。当然下方的代码是对上述示例中的WeaponUser类的重构。下方这个简单的兵工厂类WeaponFactory就是我们封装的简单工厂。其中有一个方法专门负责使用武器类型来创建武器对象,这也符合“单一职责”的原则。该方法比较简单,在此就不做过多的赘述了。下方的兵工厂类就负责生产武器对象,具体实现如下所示:
实现完我们的简单工厂后,紧接着要修改我们的WeaponUser类。WeaponUser类依赖于上面的WeaponFactory工厂类,因为WeaponUser要使用WeaponFactory来创建不同的武器对象。WeaponUser类在使用了“简单工厂”后的代码如下,以及测试用例和输出结果如下所示:
三、“工厂方法模式”(Factory Method Pattern)
第二部分的“简单工厂”模式确实简单,紧接着随着需求的变更,我们需要为上述功能添加新的需求。武器不仅仅有种类,还有生产厂家,我们要为武器添加生产厂家。比如美国造AK, 德国造AK。则我们的用户可以分为美国武器使用者(AmericaWeaponUser),德国武器使用者(GermanyWeaponUser)。在这种情况下,“简单工厂”模式已不再使用。所以我们要使用即将出场的“工厂方法模式”(Factory Method Pattern)和“策略模式”(Strategy Pattern)对上述示例进行扩充。
在对示例进行扩充之前呢,我们先看一下“工厂方法模式”的定义,其中使用到了依赖倒置原则,也就是要依赖抽象而不依赖具体类。这一点与“面相接口编程而不面相实现编程有些类似”,下方是工厂方法模式的定义。
工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
1.“工厂方法模式”的类图
如果你看完定义后,感觉抽象,那么没关系,看完下方的示例在看上述定义就会一目了然。下方就是我们要实现的“工厂方法模式”的类图,也是在上面类图的基础上进行扩充的。下方“类图”中不仅仅至于“工厂方法模式”的部分,还有我们之前介绍过的装饰者模式。为了给武器添加生产厂商,我添加了“GermanyDecorator”德国造装饰器和“AmericaDecorator”美国造装饰器,下方图中的红框部分就是我们为武器添加的装饰者,关于装饰者模式的详情请参见《“花瓶+鲜花”中的装饰模式(Decorator Pattern)》。
下方“类图”中绿框中是我们该部分的主题,也就是我们“工厂方法模式”的核心。由下方类图不难看出,与我们上面的“简单工厂模式”类比的话,下方的“工厂方法模式”是没有一个独立的的工厂的类的,但是他有一些工厂方法。这些工厂方法的实现位于WeaponUser的子类中,由子类来确定生产出什么种类的武器。这也就是上面工厂方法模式定义中所说的“将对象的实例化推迟到子类中”。“工厂方法模式”重点在于方法,利用继承关系,以及子类间的差异化类创建不同的武器类型。AmericaWeaponUser(美国武器使用者)的createWeaponWithType()工厂方法就会创建美国造武器(为武器添加上“美国造”装饰),同理GermanyWeaponUser(德国武器使用者)的createWeaponWithType()工厂方法就会创建德国造武器(为武器添加上“德国造”装饰),这就是“工厂方法模式”的核心。有一点需要注意,同一个工厂方法中生产的是同一系列的不同产品,比如美国造的各种武器,这一点与抽象工厂不同。稍后在介绍“抽象工厂模式”时会给出对比。
2. 为“武器”添加装饰者(代码实现)
首先了我们为武器添加装饰者,也就是上方类图中红框部分的“装饰者模式”的代码实现。还是那句话,因为“装饰者模式”在之前的博客中已经进行了详细的介绍,今天就不详述了,直接给出其代码实现吧。下方的代码片段就是武器的两个装饰者,如下:
3.武器用户接口的代码实现
下方截图中是武器用户接口WeaponUser的实现,接口中声明了一个赋值开火(fireWithType())的方法,和一个“工厂方法”(createWeaponWithType())。在WeaponUser中我们紧接着给出了fireWithType()方法的默认实现,在fireWithType()方法中调用了相应的“工厂方法”来获取相应的武器类型,具体实现如下。
4.“工厂方法”的具体实现
当然在“工厂方法”模式中,工厂方法的具体实现我们是推迟到相应的子类中来完成的。在该示例中就是在GermanyWeaponUser和AmericanWeaponUser中来实现具体的工厂方法。在GermanyWeaponUser(德国武器使用者)中的工厂方法给不同种类的武器添加上了“德国造”装饰器,同样在AmericanWeaponUser中的工厂方法中也做了类似的事情。具体代码如下所示:
5.测试用例
上面就是我们的全部代码了,接下来就到了我们测试的时候了,下方截图就是我们的测试用例以及输出结果。因测试用例比较简单,在此就不做过多赘述了,一句话:德国用户使用德国造,美国用户使用美国造。
四、“抽象工厂模式”(Abstract Factory Pattern)
上面我们使用到了“工厂方法模式”以及“装饰者模式”,接下来我们要做的事情就是将上面的“工厂方法模式”改为“抽象工厂模式”(Abstract Factory Pattern)。我们在改的过程中,不会动“工厂方法”一以外的部分,我们只重写第三部分中类图的绿框部分。简单的说,就是将上面的“工厂方法模式”替换为“抽象方法模式”,然后在对比两者的异同。当然本部分是“抽象工厂” + “装饰者”,而上一部分是“工厂方法” + “装饰者”。
下方是“抽象工厂的定义”
抽象工厂:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
1.设计“类图”
定义一般是不太好理解的。言归正传,还是老套路,先来看一下我们将要实现的类图是怎样的。该部分的“类图”与第三部分的类图非常相似,因为是在第三部分的类图上修改的。下方“类图”中红框的部分是我们未修改的部分,与第三部分中的类图一致。而绿框中则是我们使用“抽象工厂模式”重写后的结果。“抽象工厂”顾名思义,就是抽象了的工厂,这个抽象你可以使用接口、协议、抽象类来实现。
下方绿框中的WeaponFactoryType协议就是我们的“抽象工厂”,而AmericanWeaponFactory,GermanyWeaponFactory是其不同种类的具体实现。WeaponUser就是武器使用者,对于WeaponUser来说,它是依赖于接口,而不依赖于具体实现的,这也是设计模式的一大准则。有下方的“类图”不难看出,“抽象工厂”是一些特定工厂的集合,也就是组合的关系。具体的工厂中生产的同一品牌的不同产品。而“工厂方法”就与此不同了,“工厂方法”就是这一系列产品的具体实现。下一部分我们会在下方“抽象工厂”基础上添加上“工厂方法模式”,下方会给出具体实现,本部分还是主要了解“抽象工厂模式”。
2. 代码实现
有了类图了,我们再去实现我们的代码就容易多了。我们只需要在第三部分的基础上将“工厂方法”替换为抽象工厂即可。下方代码就是“抽象兵工厂”、“美国兵工厂”、“德国兵工厂”的实现代码。在“美国兵工厂”中使用了AmericaDecorator装饰器,在“德国兵工厂”中使用了GermanyDecorator装饰器。每个“兵工厂”都创造了其独居特色的武器装备。
实现完工厂后,我们要修改武器的使用用户。因为在“工厂方法”模式中,不同工厂武器的选择是在用户的子类中实现的,而在“抽象工厂”中就使用不到子类了。“抽象工厂”模式的用户与“简单工厂”模式的用户非常相似。下方就是我们“抽象工厂”的用户,其依赖于“抽象工厂”接口,具体实现如下所示:
3.测试用例
代码实现完了,怎么能少的了测试用例呢,下方就是我们要使用的测试用例。武器使用者可以自由切换生产武器的工厂,其实这一点类似于“策略模式”,不过不是策略模式,可以添加上一下相应的方法,将上述实现融进策略模式,再次就不做过多赘述了。下方就是我们的测试用例:
五、“工厂方法”+“抽象工厂”模式
紧接着,我们要做一件事情,就是保留第四部分中的“装饰者”、“抽象工厂”,在此基础上添加上“工厂方法”模式。也就是我们要实现装饰者+抽象工厂+工厂方法,是不是听起来很兴奋呢。上面也说了,还可以融入“策略模式”,这个就留给读者去做了。在本部分,我们将使用“工厂方法”模式来重写武器的使用者WeaponUser。这一部分很好的说明了工厂方法模式与抽象工厂模式的不同,而且两者可以结合在一起使用。
1、设计“类图”
下方这个“类图”就比较复杂了,不过不用担心,下方是在上一部分扩展而来的。与第四部分的类图对比一下,是不是只添加了黄框中的内容呢?确实如此,本部分在上面设计的基础上,我们添加了“工厂方法模式”,关于工厂方法模式的具体内容请参见本篇博客中的第三部分。红框中的装饰者模式与绿框中的“抽象工厂模式”是不变的。我们只是使用“工厂方法模式”重写了第四部分中的WeaponUser类。将WeaponUser类中选择工厂的代码推迟到了相应的子类中,有子类的工厂方法来完成相应工厂的创建。
2.代码实现
如果你看类图比较抽象的话,那么我们就看代码。在实现中我们队WeaponUser类进行的“工厂方法”的重写。提取了WeaponUser的接口WeaponUserType(对应着上述类图中的WeaponUser)。下方就是WeaponUserType的代码实现,在提取接口中,其中有一个“工厂方法”,那就是createWeaponFactroy(),如下所示:
为了代码的复用性,我们为上述接口中一些方法提供默认的实现。当然在Swift中我们使用extension为协议添加默认实现。在下方的延展中我们给出了fireWithType()和createWeaponWithType()的默认实现。具体做法如下所示。
紧接着我们要实现相应的WeaponUserType的子类,在子类中通过“工厂方法”来指定兵工厂,具体实现如下:
3.测试用例
至此,我们又在“装饰者”、“抽象工厂”的基础上添加上了“工厂方法模式”。通过上面的案例我们不难看出,“抽象工厂”是工厂的集合,“工厂方法”会指定使用工厂集合中某一个特定的工厂。下方是对本部分案例的测试用例,具体如下:
今天的博客篇幅有限,就先到到这儿了,其实上述示例还是可以加入“策略模式”的,感兴趣的读者可以尝试一下。
今天博客中的代码仍然会在Github上进行分享,分享地址为:https://github.com/lizelu/DesignPatterns-Swift