工厂模式

最近发现了一个比较好玩的射击类游戏,叫绝地求生,开局100人被投放到一个地图上,玩家可以自行决定在地图上的着陆位置,然后再地图中搜索枪械等武器,或者杀死其他玩家夺取其武器,在游戏只剩下一个人时,最后一个玩家为胜者。游戏里面有很多很多不同种类的枪,下面从研发的角度考虑玩家对枪的使用问题。

正常的情况下,一个玩家在捡起一把AKM长枪后,长枪类CAKMGun的一个对象将会被实例化,并初始化这把枪的各种参数信息,比如枪的杀伤性(应该是从数据库获取的)、枪的磨损程度、枪的重量大小、是否有子弹等等。一把枪在实际当中实例化的复杂程度应该会远远超过我们的想象。可能会使用到很多类库来协调实现。

简单的做法是,玩家(CPlayer)在捡起一把AKM后在其类内部添加一个函数,用于CAKMGun类对象的实例化和初始化。那么问题来了,玩家CPlayer其实只是想调用枪的部分接口,例如射击,装填子弹,切枪,丢弃等。CPlayer是不会在意枪是怎么生成并初始化的,如果后面的游戏维护中,AKM枪的部分参数调整了,那岂不是每次都要修改CPlayer类,CPlayer招谁惹谁了?

如果一个CPlayer在生存周期内只使用CAKMGun,那么把枪的实例化与初始化写在CPlayer中也还可以,但是在更复杂的情况下,一个玩家在生存期不只使用AKM,游戏中有上百种枪可以选择,以后游戏更新添加了一种枪就在CPlayer中添加一个实例化此种枪的对象的函数,那CPlayer会随着枪的种类的增多而无限变大,很可怕。

这个时候,我们可以把所有枪的实例化过程封装成一个类,只把枪的使用接口暴露给CPlayer(射击,装填子弹,切枪,丢弃等),CPlayer只需要调用这个生产枪支的类就能得到一把枪,然后只管调用所有枪通用接口使用枪支就好了。后期如果修改枪的实例化流程或者增加枪的种类就不需要大规模的修改CPlayer了。这种方法就叫做工厂模式。顾名思义,有一个工厂专门负责产品的生产初始化。使用户可以直接使用准备好的产品。上面的生产枪支的类就是一个工厂,而且是一个冰(兵)工厂。

 

工厂模式描述一种类,这种类对 一批或几批 功能相同的 产品 的复杂 的实例化过程进行 封装,实现产品的创建构成与产品的使用分离,使得产品可以以接口的形式提供给调用者调用。当需要从一个接口类派生出很多类,并且这些类的实例化过程都很复杂易变,可以使用工厂模式对这些类的实例化进行封装,从而减少调用者与这些类之间的耦合。

 

一、名词解释

1、迪米特法则,又叫最少知道原则,一个对象尽量对其他类尽可能少的了解,这样可以减少耦合度。

2、产品等级结构:产品等级结构即产品的继承结构。如上面的例子,一个抽象类是枪,它的子类有98K,AKM,S686,S12K,UZI,UMP9等等近百种,则抽象类“枪”与具体的枪械之间构成了一个产品等级结构,抽象类枪是父类,而具体品牌的枪械是其子类。若一个工厂生产一个产品,所有工厂生产的所有产品属于同一个产品等级结构,那么这种工厂模式就叫做工厂方法模式。

 3、产品族:分属于n个产品等级结构的n个产品因在功能上有相关性而被划分成一组(族),这组产品就是一个产品族。继续举枪的例子,98K有和其配合使用的98K子弹以及98K弹夹,AKM有和其配合使用的AKM子弹以及AKM弹夹,那么所有的枪是一个产品等级结构,所有的子弹是一个产品等级结构,所有的弹夹是一个产品等级结构,98K的枪和98K的子弹以及98K的弹夹就属于一个产品族,他们都是98K系列产品。AKM枪和子弹以及弹夹属于一个产品族,他们都是AKM系列。若一个工厂负责生产一个产品族,那么这种工厂模式就叫做抽象工厂模式。

二、特征

1、这些需要封装实例化的类应具有相同的接口特性,调用者只需要调用他们公共的接口(虚基类)就可达到预期目的。作者认为,即使这个类没在一个产品等级结构中(没有兄弟类),如果调用者实例化这个类会增加额外的依赖关系时,出于迪米特法则,工厂也是应该用的,只不过这个工厂只生产一种产品罢了。

2、这些需要封装实例化的类的实例化过程应该很复杂,需要知道凡事过犹不及,解耦造成的结果就是系统逻辑结构的复杂化,一个简单的new就能完事实例化过程的类,如果这个类属于一个产品等级结构,那么工厂模式可以考虑被使用,反之,则没有必要。

 

三、实现

1、简单工厂模式:当然是简单的写一个类,这个类没有虚基类,只对属于一个产品等级结构的所有类的复杂实例化过程进行封装。说白了就是创建一个函数(函数即可,没必要是接口)。调用者告诉这个简单的类(通过函数参数)他要用哪款产品,这个简单的类根据调用者给的参数选择生产哪款产品并返回给调用者,调用者直接使用具体产品的接口完成自己的业务逻辑。

a、简单工厂的要素

  • 产品工厂。负责所有产品(属于一个产品等级结构)的实例化,提供给产品调用者使用。
  • 产品接口。产品接口的主要目的是定义产品的规范,所有的产品实现都必须遵循产品接口定义的规范。产品接口是调用者最为关心的,产品接口定义的优劣直接决定了调用者代码的稳定性。同样,产品接口也可以用抽象类来代替,但要注意最好不要违反里氏替换原则。
  • 产品实现。实现产品接口的具体类,决定了产品在调用者使用过程中的具体行为。

b、缺陷

那么问题又来了,这种方式确实减小了调用者与产品之间的耦合,但是工厂的维护成本一样的高,对应的产品等级结构里只要有一个产品的实例化过程变了,这个简单工厂就需要改动。如果在这个产品等级结构中添加产品,那么这个简单工厂也需要改动。

 

2、工厂方法模式(针对一个产品等级结构):一个工厂类做一个具体产品的实例化,产品等级结构中的每一个类的实例化都对应一个工厂类的封装,而调用者只是实例化一个指定的工厂并调用工厂接口就可以得到想要的产品。这样封装隔离了变化(产品的增删改),降低了工厂与产品间的耦合,工厂中的case判断也不存在了(移到了应用场景中),符合开放封闭原则。一个具体产品的实例化过程变了不会影响其他产品。添加一个具体产品不会影响其他产品。

a、工厂方法的要素

  • 工厂接口。所有工厂的虚基类,与调用者直接交互来获取具体的产品。
  • 工厂实现。工厂接口的具体实现,负责具体产品的实例化,有多少个具体产品就有多少个工厂实现。
  • 产品接口。产品接口的主要目的是定义产品的规范,所有的产品实现都必须遵循产品接口定义的规范。产品接口是调用者最为关心的,产品接口定义的优劣直接决定了调用者代码的稳定性。同样,产品接口也可以用抽象类来代替,但要注意最好不要违反里氏替换原则。
  • 产品实现。实现产品接口的具体类,决定了产品在调用者使用过程中的具体行为。

b、缺陷

简单工厂和工厂方法模式都只针对一个产品等级结构,但是在现实生活中,很多个产品等级结构是一起使用的,继续上面的绝地求生,绝地求生游戏里面设计了空投,在游戏过程中系统会定期的随机的空投下一个物资补给箱,里面有一套可以直接使用武器,例如98K枪,98K子弹,98K扩容弹夹,高倍镜等等。在补给箱的实现过程中,如果使用工厂方法模式或简单工厂的话,需要调用枪的工厂,子弹的工厂,扩容弹夹的工厂等等很多工厂。并且这些在同一个补给箱的n个具体产品在实例化完成后可能会做一些复杂的初始化交互。这些交互如果在调用者中直接实现很容易增加调用者与产品间的耦合度。

 

3、抽象工厂模式(针对多个产品等级结构):一个工厂类做一个产品族的实例化,即属于n个产品等级结构的n个具体产品的实例化与初始化。多个产品等级结构间因某种内在的联系需要同时实例化具体对象并作初始化交互,这个时候需要使用抽象工厂。

a、抽象工厂的要素

  • 工厂接口。所有工厂的虚基类,提供创建各个产品的接口,与调用者直接交互来获取具体的产品。
  • 工厂实现。工厂接口的具体实现,负责分属于多个产品等级结构的具体产品的实例化与初始化交互
  • 产品接口。产品接口的主要目的是定义产品的规范,所有的产品实现都必须遵循产品接口定义的规范。产品接口是调用者最为关心的,产品接口定义的优劣直接决定了调用者代码的稳定性。同样,产品接口也可以用抽象类来代替,但要注意最好不要违反里氏替换原则。
  • 产品实现。实现产品接口的具体类,决定了产品在调用者使用过程中的具体行为。

b、缺陷

虽然抽象工厂已经做到了多产品等级结构的大规模实例化。但是产品等级结构的横向扩容问题依然无法很好的解决,当新增一个具体产品到产品族后,所有的工厂都要做产品接口的增加和初始化交互的更改。

 

三个工厂的区别:

简单工厂模式没有工厂接口,只实现少量的简单的属于一个产品等级结构的具体产品的生产。

工厂方法模式针对一个产品等级结构,每个具体工厂只实例化一个具体产品。

抽象工厂模式针对多个产品等级结构,每个具体工厂实例化一个具体产品族。

 

扩展:

对于c#/jave等高级语言,在实现简单工厂的代码里,可以使用反射机制代替if/else判断,从而使得具体创建的产品的类型的选择只依赖于输入参数,增加新的产品的时候,产品创建代码不再需要扩展修改,更加符合开闭原则。

你可能感兴趣的:(设计模式)