工厂模式是一种面向对象设计模式,定义了 工厂 的概念,软件设计时抽象产品派生出产品子类,具体的产品实例由 工厂 创建,用户从 工厂 申请需要的产品实例来使用。
简单工厂
简单工厂的概念并不存在于23种设计模式之中,学习的过程中发现其实现原理有助于理解本文后面两种设计模式。这里就列举出来。
其实现原理是,先抽象出产品的基类,然后由基类派生出各种产品子类。工厂 仅需要设计业务,由输入参数来确定创建哪种产品实例 注1。
场景列举:
假设一个场景: 某手机厂商旗下有Mate、Nova和P等品牌,不同的品牌功能相同但参数有所差异,工厂拥有生产所有品牌手机的能力。当Mate品牌发布新机,工厂需要生产Mate品牌;类似的,当其他品牌发布新机,工厂也需要生产其他品牌。那么从面向对象设计的角度,如何将此场景设计出来呢?
解决: 首先将类定义出来,涉及到Mate、Nova、P和工厂类,工厂的接口返回手机的实例。那么问题来了,从软件角度来思考,工厂的一个接口只能返回一种类的实例,如何让一个接口返回多个不同类的实例呢?C++基类虚函数可以解决这个问题注2。因此将Mate、Nova和P抽象出一个基类CPhone,工厂接口类型指定为CPhone *。加入逻辑判断,达到不同的参数返回不同的手机实例效果。
类图
简单工厂.png使用方法: 当需要生产某个品牌手机时,只需要在工厂方法getPhoneInstance指定product即可获取到某品牌实例。
缺点:
- 当增加新的手机品牌时,需要更改工厂方法getPhoneInstance来兼容新的品牌,不符合开闭原则注3。
工厂方法
由于简单工厂的缺点是不可忽视的,因此对简单工厂进行优化从而产生 工厂方法 的概念。从简单方法的类图中可以发现,工厂方法getPhoneInstance需要优化。
解决:由多个工厂子类替换掉参数的做法。将工厂类派生出多个工厂子类,一个工厂子类对应一个产品子类。如此一来,增加一个产品时,仅需要增加一个工厂子类即可,不需要对之前的逻辑进行修改。
类图:
工厂方法.png使用方法: 当需要生产某种品牌时,用户只需要由new对应的工厂子类实例,由getPhoneInstance获取此品牌实例。若增加新品牌,需要工厂派生新的工厂子类,对应的产品派生出新的产品子类,由新的工厂实例getPhoneInstance返回新的产品实例即可。
缺点:
- 当出现新的产品时,就需要发布新的工厂子类,长此以往工厂实例会越来越庞大,对使用者不友好。
抽象工厂
工厂方法多用于同一类型的产品生产,即仅存在一个抽象产品基类。假设业务需要,工厂要增加新产品(Watch)。此时 工厂方法 的设计无法满足需求,由此又提出了抽象工厂的概念。
解决方法: 新增产品基类CWatch派生出产品子类CHwGt2、CHwGt2Pro,同时在工厂基类中增加获取该子类实例的接口。如此一来,每个工厂都具备生产CPhone和CWatch的能力。
类图:
抽象工厂.png使用方法:与工厂方法类似,先new出对应的工厂子类实例,然后调用getPhoneInstance和getWatchInstance获取对应的产品实例。
缺点:
- 当新增一个不同于已存在的产品种类时(如notebook),需要在工厂基类及所有子类中增加getNoteBookInstance的接口。若存在大量的工厂子类,这种操作是存在风险的,也不符合开闭原则 注3。
总结
-
工厂方法属于创建型模式,主要用于规范类实例的创建。
-
简单工厂用法简单但是不符合开闭原则,设计中不要采用此种方式。工厂方法适合于单一类型的产品实例。抽象工厂适合于创建多个已知类型产品实例,不适用于增加新的产品类型。
-
几种工厂方式都存在一定缺陷,在长期使用过程中会创建过多子类,可能会造成代码难以维护。对于少数量的产品,是很实用的技巧。
-
还需要寻找有没有更好的技巧能够避免工厂存在的缺陷。
-
代码:https://gitee.com/dongxianghd/DesignMode/tree/master/FactoryMode
[ 注1] 一个方法返回不同的实例。
[ 注2] C++继承中可以将基类虚函数的重写,基类类型会调用子类重写的虚函数。
[ 注3] 开闭原则规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。
网友评论