Chap3:建立型设计模式————工厂方法设计模式(下)

上章讲的是建立型的设计模式,工厂方法(上),此次要讲的是另外一本书关于工厂方法的一些概念以及案例、模型等等。就像电影“风雨哈佛路”中那个老师提问,为何要用另外的一张一张纸质资料,而不直接用书籍。女主回答说,由于不一样的资料聚集了不一样人的思想。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抽象类写一个新的实现
clipboard.png

注意到上面的建立者类与产品的层次结构很类似,这是使用工厂方法模式的常见结果,造成了一种特殊的代码重复。另外一个问题是该模式可能会致使没必要要的子类化,若是你为建立者建立子类的缘由是为了实现工厂方法模式,那么最好再考虑一下(这就是为何在例子中引入页眉页脚)

抽象工厂模式

上面例子中咱们只关注了预定功能。
咱们经过加入更多编码格式,使结构“横向”增加
若是想扩展功能,使其可以处理待办事宜和联系人,那应该让它进行纵向增加
clipboard.png

实现

CommsManager抽象类定义了用于生成3个产品(ApptEncoder、TtdEncoder、ContactEncoder)的接口,咱们须要先实现一个具体的建立者,而后才能建立一个特定类型的具体产品,下图模型建立了BloggsCal格式的建立
clipboard.png

下面是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方法须要对每种状况进行考虑并进行选择


本章参考《深刻PHP:面向对象、模式与实践》第9章

相关文章
相关标签/搜索