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章

相關文章
相關標籤/搜索