PHP結構型設計模式(下)

fangle發表於2017-05-20

續上一篇:PHP結構型設計模式(上)


PHP設計模式(九)—橋接模式(Bridge Pattern)

橋接模式 (Bridge Pattern):將抽象與實現解耦,使得兩者可以獨立的變化

(一)為什麼需要橋接模式

1,如果一個系統需要在構件的抽象化角色和具體化角色之間增加更多的靈活性,避免在兩個層次之間建立靜態的繼承聯絡,通過橋接模式可以使它們在抽象層建立一個關聯關係。

2,抽象化角色和實現化角色可以以繼承的方式獨立擴充套件而互不影響,在程式執行時可以動態將一個抽象化子類的物件和一個實現化子類的物件進行組合,即系統需要對抽象化角色和實現化角色進行動態耦合。

3,雖然在系統中使用繼承是沒有問題的,但是由於抽象化角色和具體化角色需要獨立變化,設計要求需要獨立管理這兩者。對於那些不希望使用繼承或因為多層次繼承導致系統類的個數急劇增加的系統,橋接模式尤為適用。

(二)橋接模式UML圖

PHP結構型設計模式(下)
Bridge Pattern

  • Abstraction為抽象化角色,主要職責是定義該角色的行為,同時儲存一個對實現化角色的引用。
  • Implementor為實現化角色,它是一個介面或抽象類。在PHP中一般是抽象類。

有點不知所云吧。我學的時候也是。老規矩,來個例項吧。

(三)簡單例項

如果我現在是小米公司的雷布斯,小米公司旗下有小米mix和小米note手機,現在有一個底層的語音輸出軟體,由於小米mix的全面屏設計沒有開孔使用了骨傳導,所以和小米note有些不同。那麼我們要如何來設計這個輸出功能呢?

傳統的做法就應該是,一個抽象手機品牌,mix和note手機品牌繼承抽象手機品牌。然後有一個mix品牌的輸出軟體繼承自mix品牌。如果這時候有一個redmi的牌子的輸出軟體就應繼承自redmi品牌,redmi繼承抽象手機品牌。

如果除了這個語音輸出軟體,我們還有其它的軟體呢,一個手機有很多軟體,那麼這種繼承,如果畫出繼承鏈的話,那一個品牌下得有多少子類?

如果我們使用橋接模式的話,就可以把這個軟體的具體實現與抽象品牌分離出來,這樣也能是我們動態增減實現化角色時更為方便。

<?php
//抽象化角色
abstract class MiPhone{
    protected $_audio;      //存放音訊軟體物件
    abstract function output();
    public function __construct(Audio $audio){
        $this->_audio = $audio;
    }
}
//具體手機
class Mix extends MiPhone{
    //語音輸出功能
    public function output(){
        $this->_audio->output();
    }
}
class Note extends MiPhone{
    public function output(){
        $this->_audio->output();
    }
}
//實現化角色 功能實現者
abstract class Audio{
    abstract function output();
}
//具體音訊實現者 -骨傳導音訊輸出
class Osteophony extends Audio{
    public function output(){
        echo "骨傳導輸出的聲音-----哈哈".PHP_EOL;
    }
}
//普通音訊輸出---聲筒輸出
class Cylinder extends Audio{
    public function output(){
        echo "聲筒輸出的聲音-----呵呵".PHP_EOL;
    }
}

//讓小米mix和小米note輸出聲音
$mix = new Mix(new Osteophony);
$mix->output();
$note = new Note(new Cylinder);
$note->output();複製程式碼

這樣寫的好處是把抽象化角色手機和實現化角色手機的具體功能音訊輸出 分離了出來。如果現在最新的小米note系列也要用上骨傳導輸出,那麼我們只需例項化時傳入的聲筒音訊類改為骨傳導音訊類。如果我們還有一個揚聲器輸出,小米mix和小米note都有這個功能。我們只需要再新增一個揚聲器輸出類繼承Audio類,然後誰使用就儲存這個例項在屬性中。沒有橋接模式的話,我們可是要寫兩個,一個是小米mix的揚聲器輸出,一個是小米note。

如果揚聲器輸出對小米mix和小米note不一樣,那麼我們確實需要寫兩個揚聲器輸出類。使用橋接模式,依然比原來直接繼承好,就是因為有一天揚聲器技術驅動更新了,我們要更新揚聲器不用修改手機類程式碼,而只需傳入一個更新的揚聲器類。


PHP設計模式(十)—組合模式(Composite Pattern)

組合模式 (Composite Pattern):將物件組合成樹形結構以表示“部分整體”的層次結構。組合模式使得使用者對單個物件和組合物件的使用具有一致性。組合模式也叫合成模式,有時候又叫做部分-整體模式。

(一)為什麼需要組合模式

1,使我們在樹型結構的問題中,模糊了簡單元素和複雜元素的概念,客戶程式可以像處理簡單元素一樣來處理複雜元素,從而使得客戶程式與複雜元素的內部結構解耦。

2,組合模式讓你可以優化處理遞迴或分級資料結構。

(二)組合模式UML圖

PHP結構型設計模式(下)
Composite Pattern

  • Component是組合中的物件宣告介面,在適當的情況下,實現所有類共有介面的預設行為。宣告一個介面用於訪問和管理Component子部件。
  • Leaf 在組合中表示葉子結點物件,葉子結點沒有子結點。

  • Composite定義有枝節點行為,用來儲存子部件,在Component介面中實現與子部件有關操作,如增加(add)和刪除(remove)等。

(三)簡單例項

如果我們在做一個OA系統,公司的人事管理該如何設計呢。傳統的就是樹狀結構。經理下面有部門主管,然後是員工。

PHP結構型設計模式(下)
人事部門圖

<?php
class Manager{
    public $name;
    protected $c_nodes = array();//存放子節點,部門經理,普通員工等
    public function __construct($name){
        $this->name = $name;
    }
    //新增部門經理
    public function addGm(GM $gm){
        $this->c_nodes[] = $gm;
    }
    //新增普通員工
    public function addStaff(Staff $staff){
        $this->c_nodes[] = $staff;
    }
    //獲取全部子節點
    public function get_C_nodes(){
        return $this->c_nodes;
    }
}

//部門經理 就用general manager 簡寫 GM
Interface Gm{
    public function add(Staff $staff);
    public function get_c_nodes();
}
//銷售經理
class Sgm implements Gm{
    public $name;
    protected $c_nodes = array();
    public function __construct($name){
        $this->name = $name;
    }
    //新增員工
    public function add(Staff $staff){
        $this->c_nodes = $staff;
    }
    //獲取子節點
    public function get_C_nodes(){
        return $this->c_nodes;
    }
    //區別於其他經理,銷售經理有一個銷售方法
    public function sell(){
        echo "安利一下我司的產品";
    }
}
//員工介面
Interface staff{
    public function work();
}
//銷售部員工
class Sstaff implements staff{
    public $name;
    public function work(){
        echo '在銷售經理帶領下,安利全世界';
    }
}
//例項化
$manager = new Manager("總經理");
$sgm = new Sgm("銷售經理");
$staff = new Sstaff("何在");
//組裝成樹
$manager->addGm($sgm);
$sgm->add($staff);複製程式碼

我們想象一下,如果我們的層級非常深,如銷售經理下面還有,銷售主管,分割槽經理,分割槽主管。那怎麼辦?我們new的時候,就要new很多不同的類。而且如果要加一個任職期限的屬性,還得每個類去新增一遍。

我們想做的是,可以把樹形結構,當成“部分-整體結構”來處理,通俗地講,就是把一個樹形結構當成一個關係型的結構。例如:資料庫儲存的一行行的方式。找了張圖,清楚些。

PHP結構型設計模式(下)
省市儲存圖

上面這張圖把中國 -湖南-(長沙)(衡陽)-這個樹狀圖以關係型的層級方式儲存於資料庫中。對於公司人事,其實我們也是可以用這種方法的。那就是利用組合模式。再去看一眼UML圖把和人事管理圖吧。我們發現總經理和部門經理還是有很多相同的地方。組合模式主要就是把根節點和所有樹枝節點歸結到一起去,這樣就隱藏了樹形的層級。

<?php
//抽象構件
Abstract class Component{
    public $name;
    abstract function doSomething();
    public function __construct($name){
        $this->name = $name;
    }
}
//普通員工  樹葉構件 不能新增子節點
class Leaf extends Component{
    public $lever;
    public function doSomething(){
        echo "層級--{$this->lever}--work";
    }
}
//總經理 部門經理 主管等 樹枝構件
class Composite extends Component{
    public $c_nodes = array();
    public $lever = 1;
    //新增子節點
    public function add(Component $component){
        $component->lever = $this->lever + 1;
        $this->c_nodes[] = $component;
    }
    public function doSomething(){
        echo "我是層級--{$this->lever}--".PHP_EOL;
    }
}
$manager = new Composite("總經理");
$sgm = new Composite("銷售經理");
$staff = new  Leaf("何在");
//組裝成樹
$manager->add($sgm);
$sgm->add($staff);複製程式碼

這樣,我們就把根節點(總經理)和所有樹枝節點(部門經理,主管)的樹枝結構隱藏了,通過$lever屬性來區分。如果對於不同樹枝節點有不同的方法,我們也可以在Composite 類中的doSomething()方法中延遲繫結具體的方法實現,使不同層級具有不同能力

   public function doSomething(){
        switch($this->lever){
            case 1:$this->manager();
                break;
            case 2:$this->sell();
        }
    }
    private function manager(){}
    private function sell(){}複製程式碼

當然,當層級太深時,若有多個層級有相同的doSomething能力。這種方法還是可以的。但是當層級太深且不同層級具有不同的doSomething能力時,就會導致一個類中空置了多個不用的private方法,而doSomething只呼叫一個。

$lever 的另一個功能就是便於遞迴遍歷出所有公司人員

//遍歷樹 - 函式
function display(Composite $composite){
    $composite->doSomething();
    foreach($composite->c_nodes as $c_node)
         $c_node instanceof Leaf ? $c_node->doSomething() : display($c_node);                   
}
display($manager);複製程式碼

當然這種遍歷方法只能前序遍歷,即從根節點總經理向下找,沒法從任何一個員工向上找出他的上級。如果,你想實現後序遍歷,可以在Component類中新增一個parent屬性,並在composite 的add方法中設定子節點的parent屬性。

組合模式也分為透明模式和安全模式,上面的例子是安全模式。透明模式是把composite的方法也放到抽象類component中。

有許多關於分級資料結構的例子,使得組合模式非常有用武之地。關於分級資料結構的一個普遍性的例子是你每次使用電腦時所遇到的:檔案系統。檔案系統由目錄和檔案組成。每個目錄都可以裝內容。目錄的內容可以是檔案,也可以是目錄。按照這種方式,計算機的檔案系統就是以遞迴結構來組織的。如果你想要描述這樣的資料結構,那麼你可以使用組合模式Composite。


PHP設計模式(十一)—享元模式 (Flyweight Pattern)

享元模式 (Flyweight Pattern): 池技術的重要實現方式, 運用共享技術有效的支援大量的細粒度物件,用於減少建立物件的數量,以減少記憶體佔用和提高效能。

(一)為什麼需要享元模式

1,在有大量物件時,有可能會造成記憶體溢位,我們把其中共同的部分抽象出來,如果有相同的業務請求,直接返回在記憶體中已有的物件,避免重新建立。

2,系統有大量相似物件。

3,需要緩衝池的場景。

(二)享元模式UML圖

PHP結構型設計模式(下)
Flyweight Pattern

(三)簡單例項

這裡引用《設計模式之禪》的例子,就是如果有一個報考資訊系統,每個考生進來就例項化一個物件,傳入他要報考的考點。由於考生分為30個考點,所以其實我們沒有必要不停的例項化物件並釋放,我們完全可以做一個緩衝池,若緩衝池中沒有與這個考生相同考點的物件,則例項化一個,使用完不需要釋放。下一個相同考點的考生進來後,只需複用這個物件。

<?php
//抽象享元物件
abstract class Flyweight{
    //考點
    public $address;
    //享元角色必修設定考點
    public function __construct($address){
        $this->address = $address;
    }
}
//具體享元角色  考生類
class ConcreteFlyweight extends Flyweight{
    //報考動作
    public function register(){
        echo "我的報考點是:{$this->address}".PHP_EOL;
    }
    //退出
    public function quit(){
        unset($this);
    }
}
//享元工廠 緩衝池
class FlyweightFactor{
    static private $students = array();
    static public function getStudent($address){
        $students =self::$students;
        //判斷該鍵值是否存在
        if(array_key_exists($address,$students)){
            echo "緩衝池有考點為{$address},從池中直接取".PHP_EOL;   
        }else{
            echo "緩衝池沒有,建立了考點為{$address}的物件並放到池中".PHP_EOL;
            self::$students[$address] = new ConcreteFlyweight($address);
        }
        return self::$students[$address];
    }
}

//例項化學生物件
$student_1 = FlyweightFactor::getStudent('廣州');
//報考
$student_1 ->register();
// 退出
$student_1->quit();
//第二個學生進來
$student_2 = FlyweightFactor::getStudent('東莞');
//報考
$student_2 ->register();
// 退出
$student_2->quit();
//第三個學生進來
$student_3 = FlyweightFactor::getStudent('廣州');
//報考
$student_3 ->register();
// 退出
$student_3->quit();複製程式碼

這裡我們可以看到,當第三個學生進來時,由於和第一個學生的報考地點一致,所以只需從緩衝池取出。

ps:雖然ConcreteFlyweight的quit方法unset了$this,但是由於在FlyweightFactor中的students還存放著這個物件,所以unset只釋放了變數$student_1,並沒有完全刪除這個物件,這就是student_3進來還能從緩衝池取得物件的原因

享元模式在PHP中可能比較少遇,但在Java中常常有這種情況。就是程式碼產生了大量的物件,雖然使用完有釋放,但是由於垃圾回收需要一定時間,導致記憶體被耗盡。PHP經常應用web程式設計,指令碼執行時間很短(30秒)。所以很少遇見這種情況,甚至我們使用完變數,連unset()函式都不用呼叫,就等著腳步執行結束後,自動釋放。但是如果你試過在cli模式下執行PHP指令碼,做一些socket通訊,傳送郵件等長耗時或是多連線任務時,就難免會遇到這種情況。


上一篇PHP結構型設計模式(上)

感謝閱讀,由於筆者也是初學設計模式,能力有限,文章不可避免地有失偏頗
後續更新 PHP設計模式-行為型設計模式 介紹,歡迎大家評論指正


我最近的學習總結:


PHP結構型設計模式(下)
歡迎大家關注我的微信公眾號 火風鼎

相關文章