摘要:上章讲的是创建型的设计模式,工厂方法上,这次要讲的是另一本书关于工厂方法的一些概念以及案例模型等等。工厂方法模式假设你有一个关于个人事务管理的项目,功能之一是管理预约对象。
上章讲的是创建型的设计模式,工厂方法(上),这次要讲的是另一本书关于工厂方法的一些概念以及案例、模型等等。就像电影“风雨哈佛路”中那个老师提问,为什么要用另外的一张一张纸质资料,而不直接用书籍。女主回答说,因为不同的资料汇集了不同人的思想。
工厂方法模式假设你有一个关于个人事务管理的项目,功能之一是管理预约对象(Appointment)。现在要和另一个公司建立关系,需要一个叫做BloggsCal的格式来和他们交流预约相关的数据。但是你将来可能要面对更多的数据格式
在接口上可以立即定义两个类,
1.Class ApptEncoder:数据编码器,将Appointment转换成一个专有格式
2.Class CommsManager:管理员类,用来获取数据编码器,并使用编码器进行第三方通信
使用模型属于来描述的话,CommsManager就是创建者(Creator),而ApptEncoder是产品(product)
那么如何得到一个具体的ApptEncoder对象?
CommsManager类负责生成BloggsApptEncoder对象,但是当你和合作方关系改变,被要求转换系统来使用一个新的格式MegaCal时,那么代码就需要做另外的改变了
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”,因为可能会导致重复的条件语句蔓延在代码中。我们知道创建者已经能够提供交流日历数据的功能,但是如果合作方要求提供页眉和页脚来约束每次预约,那该怎么办?
结果是,你需要在上面的代码中加入新的方法
function getHeaderText(){ switch($this->mode){ case (self::MEGA): return "This is Mega format header! "; default: return "This is Bloggs format header! "; } }
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! "; } } 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 "; } function getApptEncoder(){ return new BloggsApptEncoder(); } function getTtdEncoder(){ return new BloggsTtdEncoder(); } function getContactEncoder(){ return new BloggsContactEncoder(); } function getFooterText(){ return "BloggsCal footer "; } }
在这个例子中使用了工厂方法模式,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 "; } }
类的接口更加紧凑,但也有代价,在使用工厂方法时,我们定义了一个清晰的接口强制所有具体工厂对象遵循它,而使用丹仪的make()方法,我们必须在所有的具体创建者中支持所有的产品对象。每个具体创建者都必须实现相同的标志检测(flag),客户类无法确定具体的创建者是否可以生成所有产品,因为make方法需要对每种情况进行考虑并进行选择
本章参考《深入PHP:面向对象、模式与实践》第9章
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/21259.html
摘要:利用工厂方法模式,请求者发出请求,而不具体创建产品。正是因为这个原因,使用工厂方法模式可以简化复杂的创建过程,关键就在于它在维持一个公共接口。 创建型设计模式 包括以下五种: 抽象工厂 生成器 工厂方法 原型 单例 我们选择工厂方法和原型模式作为将用PHP实现的创建型设计的例子工厂方法模式是这5个设计模式中唯一的一种类设计模式原型模式属于对象类模式,可以使用PHP_clone方法实...
摘要:设计模式设计模式基本原则设计原则按接口而不是按实现来编程按接口而不是按实现编程是指,要将变量设置为一个抽象类或接口数据类型的实例,而不是一个具体实现的实例。例如父类的一个改变会逐级向下传递给子类实现,这可能会影响子类使用的某个算法。 设计模式 设计模式基本原则 设计原则 ① : 按接口而不是按实现来编程 按接口而不是按实现编程是指,要将变量设置为一个抽象类或接口数据类型的实例,而不是一...
摘要:又称为多态性工厂模式或虚拟构造子模式。简单工厂模式简单工厂模式简单工厂模式又称为静态工厂方法模式,它属于类创建型模式。多态性设计工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。 点击进入我的博客 2.1 简单工厂模式 2.1.1 工厂模式的几种形态 工厂模式主要用一下几种形态: 简单工厂(Simple Factory):专门定义一个类来负责创建其他...
摘要:维基百科在软件工程中,创建型设计模式是用于解决对象创建机制,尝试在指定场景下使用合理的方式来创建对象的设计模式。维基百科说建造者模式是一种对象创建软件设计模式,其目的是找到一种解决方案,以解决可伸缩构造函数的反模式。 1.创建型设计模式2.结构型设计模式3.行为型设计模式 创建型设计模式 简而言之 创建型设计模式关注的是如何实例化一个或者一组相关的对象。 维基百科 在软件工程中,创建型...
摘要:创建型模式主要有以下五种简单工厂模式和工厂方法模式抽象工厂模式单例模式建造者模式原型模式在设计模式一书中将工厂模式分为两类工厂方法模式与抽象工厂模式。 一、 设计模式(Design pattern)是什么 设计模式是一套被反复使用、多数人知晓、经过分类编目的代码设计的经验总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 二、 为什么会有设计模式 在软件开发过...
阅读 3568·2021-11-25 09:43
阅读 2570·2021-11-18 13:11
阅读 2143·2019-08-30 15:55
阅读 3256·2019-08-26 11:58
阅读 2804·2019-08-26 10:47
阅读 2167·2019-08-26 10:20
阅读 1249·2019-08-23 17:59
阅读 2964·2019-08-23 15:54