上章讲的是建立型的设计模式,工厂方法(上),此次要讲的是另外一本书关于工厂方法的一些概念以及案例、模型等等。就像电影“风雨哈佛路”中那个老师提问,为何要用另外的一张一张纸质资料,而不直接用书籍。女主回答说,由于不一样的资料聚集了不一样人的思想。php
假设你有一个关于我的事务管理的项目,功能之一是管理预定对象(Appointment)。如今要和另外一个公司创建关系,须要一个叫作BloggsCal的格式来和他们交流预定相关的数据。可是你未来可能要面对更多的数据格式设计模式
在接口上能够当即定义两个类,app
1.Class ApptEncoder:数据编码器,将Appointment转换成一个专有格式
2.Class CommsManager:管理员类,用来获取数据编码器,并使用编码器进行第三方通讯this
使用模型属于来描述的话,CommsManager就是建立者(Creator),而ApptEncoder是产品(product)
那么如何获得一个具体的ApptEncoder对象?编码
<?php abstract class ApptEncoder{ //产品类 abstract function encode(); } class BloggsApptEncoder extends ApptEncoder{ //实际产品1 function encode(){ return "Appointment data encoded in BloggsCal Format\n"; } } class MegaApptEncoder extends ApptEncoder{ //实际产品2 function encode(){ return "Appointment data encoded in MegaCal Format\n"; } } class CommsManager{ //建立者(管理者) function getApptEncoder(){ return new BloggsApptEncoder(); } } ?>
CommsManager类负责生成BloggsApptEncoder对象,可是当你和合做方关系改变,被要求转换系统来使用一个新的格式MegaCal时,那么代码就须要作另外的改变了spa
class CommsManager{ const BLOGGS = 1; const MEGA = 2; private $mode =1; function __construct($mode){ $this->mode = $mode; } function getApptEncoder(){ switch($this->mode){ case (self::MEGA): return new MegaApptEncoder(); default: return new BloggsApptEncoder(); } } } $comms = new CommsManager(CommsManager::MEGA); $appt = $comms->getApptEncoder(); print $appt->encode();
在类中咱们使用常量标志定义了脚本可能运行的两个模式:MEGA和BLOGGS,在getApptEncoder()方法中使用switch语句来检查$mode属性,并实例化相关编码器设计
可是这种方法还有一种小缺陷,一般状况下,建立对象须要指定条件,可是有时候条件语句会被看成Awful的“Code taste”,由于可能会致使重复的条件语句蔓延在代码中。咱们知道建立者已经可以提供交流日历数据的功能,可是若是合做方要求提供页眉和页脚来约束每次预定,那该怎么办?code
结果是,你须要在上面的代码中加入新的方法orm
function getHeaderText(){ switch($this->mode){ case (self::MEGA): return "This is Mega format header!\n"; default: return "This is Bloggs format header!\n"; } }
Obviously,这会使得它在getApptEncoder()方法同时使用时,重复地使用了switch判断,一旦客户要增长其它需求,那工做量以及冗余程度会更重对象
总结一下当前须要思考的:
1.在代码运行时咱们才知道要生成的对象类型(BloggsApptEncoder或者是MegaApptEncoder)
2.咱们须要可以相对轻松地加入一些新的产品类型(如新的业务处理方式SyncML)
3.每个产品类型均可定制特定的功能(如上文提到的页眉页脚)
另外注意咱们使用的条件语句,其实能够被多态替代,而工厂方法模式刚好能让咱们用继承和多态来封装具体产品的建立,黄菊花说,咱们要为每种协议建立CommsManager的每个子类,而每个子类都要实现getApptEncoder方法
工厂方法模式把建立者类与要生产的产品分离开来。建立者是一个工厂类,其中定义了用于生成产品对象的类方法,若是没有提供默认实现,那么就由建立者类的子类来执行实例化。通常来讲,就是建立者类的每一个子类实例化一个相应产品子类
因此咱们把CommsManager从新指定为抽象类,这样就能够获得一个灵活的父类,并把全部特定协议相关的代码放到具体的子类中
下面是简化过的代码:
abstract class ApptEncoder{ abstract function encode(); } class BloggsApptEncoder extends ApptEncoder{ function encode(){ return "Appointment data encode in BloggsCal format!\n"; } } abstract class CommsManager{ abstract class getHeaderText(); abstract class getApptEncoder(); abstract class getFooterText(); } class BloggsCommsManager extends CommsManager{ function getHeaderText(){ return "BloggsCal Header"; } function getHeaderText(){ return new BloggsApptEncoder(); } function getFooterText(){ return "BloggsCal Footer"; } }
如今当咱们要求实现MegaCal时,只须要给CommsManager抽象类写一个新的实现
注意到上面的建立者类与产品的层次结构很类似,这是使用工厂方法模式的常见结果,造成了一种特殊的代码重复。另外一个问题是该模式可能会致使没必要要的子类化,若是你为建立者建立子类的缘由是为了实现工厂方法模式,那么最好再考虑一下(这就是为何在例子中引入页眉页脚)
上面例子中咱们只关注了预定功能。
咱们经过加入更多编码格式,使结构“横向”增加
若是想扩展功能,使其可以处理待办事宜和联系人,那应该让它进行纵向增加
CommsManager抽象类定义了用于生成3个产品(ApptEncoder、TtdEncoder、ContactEncoder)的接口,咱们须要先实现一个具体的建立者,而后才能建立一个特定类型的具体产品,下图模型建立了BloggsCal格式的建立
下面是CommsManager和BloggsCommsManager的代码
abstract class CommsManager{ abstract function getHeaderText(); abstract function getApptEncoder(); abstract function getTtdEncoder(); abstract function getContactEncoder(); abstract function getFooterText(); } class BloggsCommsManager extends CommsManager{ function getHeaderText(){ return "BloggsCal header\n"; } function getApptEncoder(){ return new BloggsApptEncoder(); } function getTtdEncoder(){ return new BloggsTtdEncoder(); } function getContactEncoder(){ return new BloggsContactEncoder(); } function getFooterText(){ return "BloggsCal footer\n"; } }
在这个例子中使用了工厂方法模式,getContactEncoder()是CommsManager的抽象方法,并在BloggsCommManager中实现。设计模式间常常会这样写做:一个模式建立能够把它本身引入到另外一个模式的上下文环境中,咱们加入了对MegaCal格式的支持
这样的模式带来了什么?
1.系统与实现的细节分离开来,咱们能够在实例中添加移除任意树木的编码格式而不会影响系统
2.对系统中功能相关的元素强制进行组合,所以经过使用BloggsCommsManager,能够确保值使用与BloggsCal相关的类
3.添加新产品比较麻烦,不只要建立新产品的具体实现,并且必须修改抽象建立者和它的每个具体实现
咱们能够建立一个使用标志来决定返回什么对象的单一make()方法,而不用给每一个工厂方法建立独立的方法,以下
abstract class CommsManager{ const APPT = 1; const TTD = 2; const CONTACT = 3; abstract function getHeaderText(); abstract function make($flag_int); abstract function getFooterText(); } class BloggsCommsManager extends CommsManager{ function getHeaderText(){ return "BloggsCal header"; } function make($flag_int){ switch($flag_int){ case self::APPT: return new BloggsApptEncoder(); case self::CONTACT: return new BloggsContactEncoder(); case self::TTD: return new BloggsTtdEncoder(); } } function getFooterText(){ return "BloggsCal footer\n"; } }
类的接口更加紧凑,但也有代价,在使用工厂方法时,咱们定义了一个清晰的接口强制全部具体工厂对象遵循它,而使用丹仪的make()方法,咱们必须在全部的具体建立者中支持全部的产品对象。每一个具体建立者都必须实现相同的标志检测(flag),客户类没法肯定具体的建立者是否能够生成全部产品,由于make方法须要对每种状况进行考虑并进行选择