php中的組合模式

剛看完了《深刻php面向對象、模式與實踐》一書中組合模式這塊內容,爲了加深理解和記憶,因此着手寫了這篇博客。php

爲方便後續理解,此處先引入兩個概念,局部對象和組合對象。sql

局部對象:沒法將其餘對象組合到自身內部屬性上的對象。即不能組合其餘對象的對象。數據庫

組合對象:能夠將其餘對象組合到自身內部屬性上的對象。便可以組合其餘對象的對象。數組

注:將對象A的某個屬性中存儲着對象B對象的引用,則表示A與B有組合關係,其中A將B組合到了自身內部。緩存

 

首先咱們經過給出下面的業務需求,來引入組合模式:安全

業務部門想要開發一款相似於紅色警惕的戰鬥遊戲,初始局部戰鬥單元有兩個:射手(Archer)和激光炮(LaserCannon),組合戰鬥單元能夠有多種(由兩個局部單元組合成的組合戰鬥單元,或者由組合單元和局部戰鬥單元組合成的組合戰鬥單元,這裏隨着子對象的不一樣,組合對象有多種)。其中組合單元和局部單元都具備戰鬥單元共有的屬性(攻擊能力,移動能力和防護能力)。而組合單元中的屬性值是其子對象對應屬性值的和。由於組合對象的子對象須要根據狀況不斷變化,那麼其對應的屬性值也須要不斷更新和變化,這裏咱們須要採用什麼樣的組合方式考才能方便的更新和獲取組合對象的屬性值呢,這裏就須要用到咱們今天講的組合模式了。數據結構

組合模式:函數

一、定義:將一組對象組合爲可像單個對象同樣被使用的結構。(即組合對象中獲取屬性值可如局部對象中同樣方便)學習

二、分類:組合模式分爲兩種,透明模式和安全模式this

二、實現方法:

分析:組合對象如何才能夠很方便計算出屬性值呢?若是能夠很方便獲取其子對象中的對應屬性,則能夠經過求和得出。那麼如何方便的獲得子對象中的對應屬性值呢?這裏主要挑戰在於如何知道包含哪些子對象呢,固然最簡單的方法就是在組合對象中保留子對象的引用,將全部子對象存儲在一個數據結構爲數組的屬性中,這樣咱們就能夠在獲取屬性值的方法中循環遍歷該數組,而後經過求和算出組合對象的屬性值。具體代碼以下:

abstract class Unit
{
    // 用來獲取戰鬥單元的攻擊屬性值
    abstract function bomstrength();
}

// 局部戰鬥單元(射手)
class Archer extends Unit
{
    function bomstrength()
    {
        return 4;
    }
}
//局部戰鬥單元(激光炮)
class LaserCannonUnit extends Unit
{
    function bomstrength()
    {
        return 44;
    }
}

// 組合戰鬥單元
class Army extends Unit { private $units = array();//用來存儲子對象 // 用來添加子對象 function addUnit(Unit $unit) { array_push($this->units, $unit); } // 用來計算攻擊屬性值 function bomstrength() { $ret = 0; foreach($this->units as $unit) { $ret += $unit->bomStrength(); } } }

上面正是知足咱們需求的代碼,可是更多時候客戶端不須要區分對象是Army、Unit仍是其餘組合對象,就功能上,這些組合模式是相同的,都具備移動、攻擊和防護的功能。這些特色讓咱們很容易想到,讓他們共享同一個類型家族(這就是組合模式)。

下面是組合模式下透明和安全兩種方式的實現方式:

透明模式:

abstract class Unit
{
    abstract functon addUnit (Unit $unit);
    abstract function removeUnit (Unit $unit);
    abstract function bomStrength();
}

Army class extends Unit
{
    private $units =array();
    
    public function addUnit(Unit $unit)
    {
        if(in_array($unit, $this->units, true))
        {
            return;
        }
    }

    public function removeUnit($unit)
    {
        $this->units = array_udiff($this->units, array($unit), function($a, $b) { return ($a===$b) ?0:1});
    }

    public function bomStrength()
    {
        $ret = 0;
        foreach($this->units as $unit)
        {
            $ret += $unit->bomStrength(); 
        }
    }

}

這裏Army能夠保存任何類型的Unit對象,包括Army自己或者Archer或者LaserCannon這樣的局部對象。由於全部Unit對象都保證支持bomStrength方法,因此咱們Army::bomStrength只需遍歷$units發展,調用每一個Unit對象的bomStrength方法,就能夠計算出帶動軍隊的攻擊強度了。

可是實際上,咱們不須要在Archer上添加Unit對象,因此當Archer或者LaserCannon這種局部對象調用addUnit或者removeUnit方法時須要拋出異常,咱們能夠在抽象類中指定這兩個方法拋出異常,只在組合對象中重寫這兩個函數。這種就是組合模式中的透明模式,特色就是對客戶端透明,不管是局部對象仍是組合對象,方法是公有的,不過這裏有很明顯的缺點:當局部對象調用removeUnit或者addUnit方法時雖然方法存在,可是隻會給出異常,這是咱們在運行中不想遇到的,有沒有方法能夠實現共用一個父類,同時不會出現不可預期的報錯行爲的方法呢。固然有,這就量組合模式的另外一種模式,安全模式。

安全模式:

abstract class Unit
{
function getComposite()
{
return null;
}
abstract function bomStrength(); } class CompositeUnit extends Unit { private $units = array(); function getComposite() { return $this; } protected function units() { return $this->units; } public function removeUnit(Unit $unit) { $this->units = array_udiff($this->units, array($unit), function($a, $b) {return ($a===$b)?0:1} ); } public function addUnit(Unit $unit) { if(in_array($unit, $this->units, true)) { return; } $this->units[] = $unit; } }

如上所示:咱們爲組合對象添加了一個子抽象類,這個子抽象類中多了個方法getComposite,這個方法用於客戶端識別是否爲組合對象,若是是的話,返回這個 對象,若是不是,則返回null,這樣客戶端在調用aaUnit或者removeUnit方法前調用getComposite方法便可知道對象的類型,並做出適當的操做,完美的解決了透明模式中出現的問題。

可是這兩種模式自己沒有孰優孰劣,具體使用哪一種,須要根據業務來區分。

須要擔憂組合模式的成本若是子類嵌套太多,可能一個循環就把系統搞崩了,因此這裏給出使用組合模式的技巧:

1)須要在父集對象中將子類的屬性值緩存下來,這樣能夠減小系統開銷,即使如此,還須要保證緩存值不會過時,即須要實時更新過時緩存。

2)對象持久化上,組合模式不適合存儲在關係型數據庫中,由於這樣隨着系統嵌套的深度加大,sql查詢的開銷就會越大,這樣更新添加和移除子對象時的系統開銷就會比較大。不過組合模式中的對象關係適合存放在xml中,xml中樹形結構和組合對象中的樹形結構正好匹配。

 

以上就是本人對組合模式的初步理解,感謝您的閱讀。

注:因本人的技術有限,若是有理解錯誤的地方,還請各位批評指正,共同交流學習,謝謝。我會繼續努力的。

相關文章
相關標籤/搜索