成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

【面向?qū)ο蟮腜HP】之模式:組合

546669204 / 2961人閱讀

摘要:我們可以做一些小改進(jìn)將的拋出異常代碼挪入父類屬于最小單位。完整代碼當(dāng)我們需要在某個子類,實(shí)現(xiàn)個性化的業(yè)務(wù)邏輯時,組合模式的缺陷之一正在顯現(xiàn)出來簡化的前提是所有的類都繼承同一個基類,簡化優(yōu)點(diǎn)有時是以降低對象安全為代價。

開篇

如果你注意了目錄,會知道:組合是一個新的開始。
在系統(tǒng)代碼設(shè)計的過程中,我們通過繼承來組織代碼,父類與子類,實(shí)質(zhì)上對應(yīng)了業(yè)務(wù)的整體規(guī)范與具體需求。所以,我們需要將類按照某種邏輯組合起來,從而讓類成為一個集合化的體系。
組合模式,描述的就是這種邏輯——當(dāng)我們需要通過規(guī)范的操作,來聯(lián)系一些類,甚至將其格式化為父子層級關(guān)系時,我們有哪些模式(“工具”)可用。

問題

管理一組對象的復(fù)雜性比較高,從外部通過理論的方式去詮釋它,難度更大。為此,這里設(shè)計一個虛構(gòu)場景:
在前面的模式中,我們使用了一個類似文明游戲的場景,現(xiàn)在繼續(xù)使用它,在這里,我們要實(shí)現(xiàn)一個簡易的戰(zhàn)斗單位組成系統(tǒng)。

先定義一些戰(zhàn)斗單元的類型:

abstract class Unit {
    abstract function bombardStrength();
}

class Archer extends Unit {
    function bombardStrength()
    {
        return 3;
    }
}

class LaserCannonUnit extends Unit {
    function bombardStrength()
    {
        return 10;
    }
}

我們設(shè)計了一個抽象方法bombardStrength,用于設(shè)置戰(zhàn)斗單位的傷害,并且通過繼承實(shí)現(xiàn)了兩個具體的子類:ArcherLaserCannonUnit,完整的類自然應(yīng)該包含移動速度、防御等內(nèi)容,但你能發(fā)現(xiàn)這是同質(zhì)化的,所以我們?yōu)榱耸纠a的簡單,省略掉它。

下面,我們創(chuàng)建一個獨(dú)立類,來實(shí)現(xiàn)戰(zhàn)斗單元的組合(軍隊(duì))。

class Army {
    private $units = array();

    function addUnit( Unit $unit ) {
        array_push($this->units, $unit);
    }

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

        return $ret;
    }
}

Army類的addUnit方法用于接收單位,通過bombardStrength方法來計算總傷害。但我想如果你對游戲有興趣,就不會滿足于這樣一個粗糙的模型,我們來添點(diǎn)新東西:我軍/盟軍拆分(目前它們?nèi)绻旌显谝黄?,就無法再區(qū)分部隊(duì)歸屬)

class Army {
    private $units = array();
    private $armies = array();

    function addUnit( Unit $unit ) {
        array_push($this->units, $unit);
    }

    function addArmy( Army $army ) {
        array_push( $this->armies, $army );
    }

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

        foreach ( $this->armies as $army ) {
            $ret += $army->bombardStrength();
        }

        return $ret;
    }
}

所以現(xiàn)在,這個Army類不但可以合并軍隊(duì),更可以在需要時,將處于一支軍隊(duì)的盟我部隊(duì)拆分開。

最后,我們觀察寫好的這些類,他們都具備同一個方法bombardStrength,并且在邏輯上,也具備共同點(diǎn),所以我們可以將其整合為一個類的家族。

實(shí)現(xiàn)

組合模式采用單根繼承,下面放出UML:

可以看到,所有的軍隊(duì)類都源于Unit,但這里有一個注解:Army、TroopCarrier類為組合對象,ArcherLaserCannon類則是局部對象樹葉對象。

這里額外描述一下組合模式的類結(jié)構(gòu),它是一種樹形結(jié)構(gòu),組合對象為枝干,可以開出相當(dāng)數(shù)量的葉子,樹葉對象則是最小單位,其內(nèi)部無法包含本組合模式的其他對象。

這里有一個問題:局部對象是否需要包含addUnit、removeUnit之類的方法,在這里我們?yōu)榱吮3忠恢滦?,后面再討論?/p>

下面我們開始實(shí)現(xiàn)Unit、Army類,觀察Army可以發(fā)現(xiàn),它可以保存所有的Unit衍生的類實(shí)例(對象),因?yàn)樗鼈兙邆湎嗤姆椒?/strong>,需要軍隊(duì)的攻擊強(qiáng)度,只要調(diào)用攻擊強(qiáng)度方法,就可以完成匯總。

現(xiàn)在,我們面對的一個問題是:如何實(shí)現(xiàn)add、remove方法,一般組合模式會在父類中添加這些方法,這確保了所有衍生類共享同一個接口,但同時表示:系統(tǒng)設(shè)計者將容忍冗余。

這是默認(rèn)實(shí)現(xiàn)方法:

class UnitException extends Exception {}

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

class Archer extends Unit {
    function addUnit( Unit $unit ) {
        throw new UnitException( get_class($this) . " 屬于最小單位。");
    }

    function removeUnit( Unit $unit ) {
        throw new UnitException( get_class($this) . " 屬于最小單位。");
    }

    function bombardStrength()
    {
        return 3;
    }
}

class Army extends Unit {
    private $units = array();

    function addUnit( Unit $unit ) {
        if ( in_array( $unit, $this->units ,true)) {
            return;
        }
        $this->units[] = $unit;
    }

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

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

        return $ret;
    }
}

我們可以做一些小改進(jìn):將add、remove的拋出異常代碼挪入父類:

abstract class Unit {
    function addUnit( Unit $unit ) {
        throw new UnitException( get_class($this) . " 屬于最小單位。");
    }

    function removeUnit( Unit $unit ) {
        throw new UnitException( get_class($this) . " 屬于最小單位。");
    }

    abstract function bombardStrength();
}

class Archer extends Unit {
    function bombardStrength()
    {
        return 3;
    }
}
組合模式的益處

靈活:組合模式中的所有類都共享了同一個父類型,所以可以輕松的在設(shè)計中添加新的組合對象或局部對象,而無需大范圍修改代碼。

簡單:使用組合模式,客戶端代碼只需設(shè)計簡單的接口。對客戶端來說,調(diào)用需要的接口即可,不會出現(xiàn)任何“調(diào)用不存在接口”的情況,最少,它也會反饋一個異常。

隱式到達(dá):對象通過樹形結(jié)構(gòu)組織,每個組合對象都保存著對子對象的引用,因此,書中某部分的一個小操作,可能會產(chǎn)生很大影響,卻不為人知——譬如:我們將軍隊(duì)1名下的一支軍隊(duì)(a),挪動到軍隊(duì)2,實(shí)際上挪動的是軍隊(duì)(a)中所有的軍隊(duì)個體。

顯示到達(dá):樹形結(jié)構(gòu)可以輕松遍歷,可以快捷的通過迭代樹形結(jié)構(gòu),來獲取包含對象的信息。

最后,我們做一個Small Test吧。

// 創(chuàng)建番號
$myArmy = new Army();
// 添加士兵
$myArmy->addUnit( new Archer() );
$myArmy->addUnit( new Archer() );
$myArmy->addUnit( new Archer() );

// 創(chuàng)建番號
$subArmy = new Army();
// 添加士兵
$subArmy->addUnit( new Archer() );
$subArmy->addUnit( new Archer() );

$myArmy->addUnit( $subArmy );

echo "MyArmy的合計傷害為:" . $myArmy->bombardStrength(); // MyArmy的合計傷害為:15
效果

來讓我解釋一下:為何addUnit之類的方法,必須出現(xiàn)在局部類中,因?yàn)槲覀円3?b>Unit的透明性——客戶端在進(jìn)行任何訪問時,都清楚的知道:目標(biāo)類中肯定有addUnit或其他方法,而不需要去猜疑。

現(xiàn)在,我們將Unit類解析出一個抽象子類CompositeUnit,并將組合對象具備的方法挪到它身上,加入監(jiān)測機(jī)制:getComposite。

現(xiàn)在,我們解決了“冗余方法”,只是我們每次調(diào)用,都必須通過getComposite確認(rèn)是否為組合對象,并且按照這種邏輯,我們可以寫一段測試代碼。

完整代碼:

class UnitException extends Exception {}

abstract class Unit {
    function getComposite() {
        return null;
    }

    abstract function bombardStrength();
}

abstract class CompositeUnit extends Unit {
    private $units = array();

    function getComposite() {
        return $this;
    }

    protected function units() {
        return $this->units;
    }

    function addUnit( Unit $unit ) {
        if ( in_array( $unit, $this->units ,true)) {
            return;
        }
        $this->units[] = $unit;
    }

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

class UnitScript {
    static function joinExisting( Unit $newUnit, Unit $occupyingUnit ) {
        if ( !is_null( $comp = $occupyingUnit->getComposite() ) ) {
            $comp->addUnit( $newUnit );
        } else {
            $comp = new Army();
            $comp->addUnit( $occupyingUnit );
            $comp->addUnit( $newUnit );
        }

        return $comp;
    }
}

當(dāng)我們需要在某個子類,實(shí)現(xiàn)個性化的業(yè)務(wù)邏輯時,組合模式的缺陷之一正在顯現(xiàn)出來:簡化的前提是所有的類都繼承同一個基類,簡化優(yōu)點(diǎn)有時是以降低對象安全為代價。為了彌補(bǔ)損失的安全,我們需要進(jìn)行類型檢查,直到有一天你會發(fā)現(xiàn):我們做了太多的檢查工作——甚至已經(jīng)開始顯著影響到代碼效率。

class TroopCarrier {
    function addUnit(Unit $unit) {
        if ($unit instanceof Cavalry) {
            throw new UnitException("不能將馬放置于船上");
            super::addUnit($unit);
        }
    }

    function bombardStrength() {
        return 0;
    }
}

組合模式的優(yōu)點(diǎn)正在不斷被數(shù)量越來越多的特殊對象所沖抵,只有在大部分局部對象可互換的情況下,組合模式才最適用。

另一個揪心的問題是:組合對象的操作成本,如果你玩過最高指揮官或者橫掃千軍,就會明白這個問題的嚴(yán)重性,當(dāng)你擁有了上千個戰(zhàn)斗單位,并且這些單位本身還分別屬于不同的番號,你每次計算某個軍隊(duì)數(shù)值,都會帶來龐大的軍隊(duì)開銷,甚至是系統(tǒng)崩潰。

我相信我們都能想到:在父級或最高級對象中,保存一個緩存,這樣的解決方法,但實(shí)際上除非你用精度極高的浮點(diǎn)數(shù),否則要小心緩存的有效性(尤其是像JS一類的語言,為了做一系列的游戲數(shù)值緩存,我曾忽略了它的數(shù)值換算誤差)。

最后,對象持久化上需要注意:1. 雖然組合模式是一個優(yōu)雅的模式,但它并不能將自身輕松的存儲到關(guān)系型數(shù)據(jù)庫中,你需要通過多個昂貴的查詢,來將整個結(jié)構(gòu)保存在數(shù)據(jù)庫中;2. 我們可以通過賦予ID來解決 1. 的問題,可仍需要在獲取對象后,重建父子引用關(guān)系,這會讓它變得略顯混亂。

小結(jié)

如果你想“如同操作一個對象般的隨心所欲”,那么組合模式的是你所需要的。

但,組合模式依賴于組成部分的簡單性,隨著我們引入復(fù)雜規(guī)則,代碼會變得越來越難以維護(hù)。

額外的:組合模式不能很好地保存在關(guān)系數(shù)據(jù)庫,但卻非常適合使用XML進(jìn)行持久化。

(持久化 = 保存)
(父類 = 超類,因?yàn)橛⑽亩际荢uperClass,額外的,你可能喜歡“直接繼承”、“間接繼承”的概念)

小疑惑

我發(fā)現(xiàn),外國人往往采用實(shí)際應(yīng)用來教學(xué),尤其是游戲之類的非常有趣的應(yīng)用,不知這是國外教學(xué)的傳統(tǒng),還是我錯位的理解。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/22947.html

相關(guān)文章

  • 面向對象PHP模式:目錄

    摘要:系列目錄生成對象的模式面向?qū)ο蟮闹J絾卫嫦驅(qū)ο蟮闹J焦S方法面向?qū)ο蟮闹J匠橄蠊S方法面向?qū)ο蟮闹J皆徒M合類對象的模式面向?qū)ο蟮闹J浇M合更新裝飾模式 系列目錄 生成對象的模式 【面向?qū)ο蟮腜HP】之模式:單例 【面向?qū)ο蟮腜HP】之模式:工廠方法 【面向?qū)ο蟮腜HP】之模式:抽象工廠方法 【面向?qū)ο蟮腜HP】之模式:原型 組合類/對象的模式 【面向?qū)ο蟮腜HP】之模式...

    lieeps 評論0 收藏0
  • php設(shè)計模式

    摘要:我們今天也來做一個萬能遙控器設(shè)計模式適配器模式將一個類的接口轉(zhuǎn)換成客戶希望的另外一個接口。今天要介紹的仍然是創(chuàng)建型設(shè)計模式的一種建造者模式。設(shè)計模式的理論知識固然重要,但 計算機(jī)程序的思維邏輯 (54) - 剖析 Collections - 設(shè)計模式 上節(jié)我們提到,類 Collections 中大概有兩類功能,第一類是對容器接口對象進(jìn)行操作,第二類是返回一個容器接口對象,上節(jié)我們介紹了...

    Dionysus_go 評論0 收藏0
  • php設(shè)計模式

    摘要:我們今天也來做一個萬能遙控器設(shè)計模式適配器模式將一個類的接口轉(zhuǎn)換成客戶希望的另外一個接口。今天要介紹的仍然是創(chuàng)建型設(shè)計模式的一種建造者模式。設(shè)計模式的理論知識固然重要,但 計算機(jī)程序的思維邏輯 (54) - 剖析 Collections - 設(shè)計模式 上節(jié)我們提到,類 Collections 中大概有兩類功能,第一類是對容器接口對象進(jìn)行操作,第二類是返回一個容器接口對象,上節(jié)我們介紹了...

    vspiders 評論0 收藏0
  • PHP經(jīng)驗(yàn)總結(jié) - 聊聊面向對象

    摘要:例如汽車這個名詞可以理解為汽車的總類,但這輛寶馬汽車則是一個具體的汽車對象。當(dāng)在類成員方法內(nèi)部調(diào)用的時候,可以使用偽變量調(diào)用當(dāng)前對象的屬性。在面向?qū)ο笾袆t被稱之為方法。 簡述 現(xiàn)在大伙都在講面向?qū)ο缶幊蹋俏覀円驳孟日抑粋€對象是不?不然怎么面向?qū)ο??怎么編程?--- 笑話一則,但是理不虧,要搞P面向?qū)ο缶幊蹋覀兤鸫a要先搞懂對象(還有類)是什么?只有了解它,理解它,你才能駕馭它。...

    lpjustdoit 評論0 收藏0
  • PHP代碼簡潔道——類和對象部分

    摘要:使用和在中,通過為屬性或方法設(shè)置和關(guān)鍵字可以實(shí)現(xiàn)對屬性或方法的可見性控制。你的繼承表達(dá)了一個對等比如人類是動物的關(guān)系,不是包含的關(guān)系比如用戶具有用戶詳情你能從基類中復(fù)用代碼你想通過修改全局類來對所有派生類進(jìn)行修改。 使用getter和setter 在 PHP 中,通過為屬性或方法設(shè)置 public, protected 和 private 關(guān)鍵字可以實(shí)現(xiàn)對屬性或方法的可見性控制。不過,...

    cyixlq 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<