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中树形结构和组合对象中的树形结构正好匹配。

 

以上就是本人对组合模式的初步理解,感谢您的阅读。

注:因本人的技术有限,若是有理解错误的地方,还请各位批评指正,共同交流学习,谢谢。我会继续努力的。

相关文章
相关标签/搜索