php中的简单工厂模式、工厂模式、抽象工厂模式
距离上次更新博客已经过去10天了,按计划这篇博客早该更新了,可计划赶不上变化由于事情太多,导致该计划不断延期,这不终于有块空闲时间了,得赶紧补上。哈哈。
好了,言归正传,今天我给大家说说php中的工厂模式。
工厂模式可分为简单工厂、工厂和抽象工厂,具体区别是什么呢?下面我们通过实例来一步一步讲解:
首先我们设想一个任务场景:
假设有个关于个人事务管理的项目,功能之一就是管理Appointment(预约)对象。我们的业务团队和A公司建立了关系,目前需要使用一个叫做BloggsCal格式来和他们交流预约相关的数据。但是业务部门提醒可能会有更多的数据格式。
任务分析:
因为解码器可能会有多种,为了避免在逻辑代码中使用过多的if else,这里需要使用工厂模式来将创造者和使用者分开,此时需要两个类,一个类AppEncoder用于定义一个解码器,将A公司传来的数据解码;另外一个类CommsManager用于获取该解码器,用于与A公司进行通信。使用模式术语说,CommsManager就是创造者,AppEncoder就是产品。(一个创造者、一个产品,将类的实例化和对象的使用分离开,这就是工厂模式的思想)
上面的任务场景我们可以通过简单工厂模式实现,具体代码如下:
1 <?php 2 //产品类 3 class BloggsApptEncoder { 4 function encode() 5 { 6 return "Appointment data encoded in BloggsCal format\n"; 7 } 8 } 9 10 //创造者类 11 class CommsManager { 12 function static getBloggsApptEncoder() 13 { 14 return new BloggsApptEncoder();
15 }
16 }
使用方法比较简单,这里不做赘述。
此时又有新的需求,业务部门告诉我们需要新增一种数据格式MegCal,来完成数据交流。此时我们需要新增对应的解码器类,然后直接在commsManager新增参数来标识需要实例化哪个解码器。代码如下:
<?php class CommsManager { const BLOGGS = 1; const MEGA = 2; private $mode; public function __construct( $mode ) { $this->mode = $mode; } function getApptEncoder() { switch($this->mode) { case (self::MEGA): return new MegaApptEncoder(); default: return new BloggsApptEncoder(); } } }
这便是简单工厂模式了。那么它带来了什么好处呢?
首先,符合现实中的情况;而且客户端免除了直接创建产品对象的责任,而仅仅负责“消费”产品(正如暴发户所为)。
下面我们从开闭原则上来分析下简单工厂模式。当新增一种数据格式的时候,只要符合抽象产品格式,那么只要通知工厂类知道就可以被使用了。(即创建一个新的解码器类,继承抽象解码器ApptEncoder)那么对于产品部分来说,它是符合开闭原则的——对扩展开放、对修改关闭;但是工厂类不太理想,因为每增加一各格式,都要在工厂类中增加相应的商业逻辑和判断逻辑,这显自然是违背开闭原则的。
而在实际应用中,很可能产品是一个多层次的树状结构。由于简单工厂模式中只有一个工厂类来对应这些产品,所以这可能会把我们的上帝类坏了。
因此简单工厂模式只适用于业务简单的情况下或者具体产品很少增加的情况。而对于复杂的业务环境可能不太适应了。这就应该由工厂方法模式来出场了!!
业务部门的新需求来了:
每种格式的预约数据中,需要提供页眉和页脚来描述每次预约
扩展之前我们用简单工厂模式来实现上述功能,具体代码如下:
// 简单工厂模式
class CommsManager { const BLOGGS = 1; const MEGA = 2; private = $mode; public function __construct( $mode ) { $this->mode = $mode; } // 生成解码器对应的页眉 public function getHeaderText() { switch( $this->mode ) { case ( self::MEGA ): return "MegaCal header\n"; default: return "BloggsCal header\n"; } } // 生成解码器 public function getApptEncoder() { switch( $this->mode ) { case ( self::MEGA ): return new MegaApptEncoder(); default: return new BloggsApptEncoder();; } } }
可见:此时相同的条件语句switch在不同的方法中出现了重复,而且如果添加新的数据格式需要改动的类过多。所以需要对我们的结构进行修改,以求更容易扩展和维护,我们可以使用创造者子类分别生成对应的产品,这样添加新的数据格式时,只需要添加一个创造者子类即可,方便扩展和维护。具体代码如下:
// 工厂模式
abstract class CommsManager { abstract function getHeaderText(); abstract function getApptEncoder(); abstract function getFooterText(); } class BloggsCommsManager extends CommsManager { function getHeaderText() { return "BloggsCal Header\n"; } function getApptEncoder() { return new BloggsApptEncoder(); } function getFooterText() { return "BloggsCal Footer\n"; } } class MegaCommsManager extends CommsManager { function getHeaderText() { return "MegaCal Header\n"; } function getApptEncoder() { return new MegaApptEncoder(); } function getFooterText() { return "MegaCal Footer\n"; } }
此时,如果有新的数据格式,只需要添加一个创造类的子类即可。此时想获取MegaCal对应的解码器直接通过MegaCommsManager::getApptEncoder()获取即可;当前架构已经可以满足目前的需求,但是别得意哦,这些讨厌的产品又来需求了,他们不仅需要和A公司交流预约数据(Appointment),还需要交流待办事宜(Ttd)、联系人(Contact)等数据。同样的这些数据交流的格式也是BloggsCal和MegaCal.那么我们该如何设计呢?直接在对应解码器的子类中添加处理事宜(TtD)和联系人(Contact)的方法,代码如下:
// 抽象工厂模式
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"; } } class MegaCommsManager extends CommsManager { function getHeaderText() { return "MegaCal Header\n"; } function getApptEncoder() { return new MegaApptEncoder(); } function getTtdEncoder() { return new MegaTtdEncoder(); } function getContactEncoder() { return new MegaContactEncoder(); } function getFooterText() { return "MegaCal Footer\n"; } } //当然需要添加对应的TtdEncoder抽象类和ContactEncoder抽象类,以及他们的子类。
上面就是工厂模式和及其变形,核心在于:
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_init ); abstract function getFooterText(); } class BloggsCommsManager extends CommsManager { function getHeaderText() { return "BloggsCal Header\n"; } function make( $flag_init ) { switch ($flag_init) { case self::APPT: return new BloggsApptEncoder(); case self::TTD: return new BloggsTtdEncoder(); case self::CONTACT: return new BloggsContactEncoder(); } } function getFooterText() { return "BloggsCal Header\n"; } }
如果还需要添加交流其它的数据,此时只需在抽象创造者中添加一个新的flag_init标识,并在子创造者中的make方法中添加一个条件。相比来说比原先的更加容易扩展。只需修改少数地方即可。
总结:
简单工厂:适用于生成数量少,功能简单的产品(BloggApptEncoder和MegaApptEncoder)
工厂模式:适用于生成数量多,功能复杂的产品(多个产品树[BloggCal,MegaCal]、单个产品族[apptEncoder]),相比简单工厂来说:业务更复杂,功能更多,但是产品族还是单个。
抽象工厂:适用于生成多个产品族、多个产品树的情景(产品族[appt,ttd,contact],产品树[Bloggcal,megaCal])。相比于工厂模式,更容易扩展添加新的产品族。
以上就是我对php中简单工厂、工厂模式和抽象工厂的初步理解,感谢您的阅读
注:因本人的技术有限,如果有理解错误的地方,还请各位批评指正,共同交流学习,谢谢。我会继续努力的。