【PHP设计模式】结构型之组合(Composite)

原型模式(Composite)

意图:

  【GoF】将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性

动机:

  将一组对象组合为像单个对象一样被使用的结构。 组合模式可以很好的聚合和管理许多相似的对象,在客户端用户看来,组合对象和独立对象没有什么区别。将单个对象看成物体,将对象组合看成容器。组合模式可以对容器和物体进行,其他对象在处理这些对象的时候,不用追究谁是容器,谁是物体。就比如说一个军队里面的一支分队和分队里面的每个士兵,因为 他们都有共同的方法,就是攻击力。我们把这个分队看成是容器,把士兵看成物体,那么这个军队就是分队和士兵的基类,也就是接口!接口中定义这个攻击力的方 法,与增加对象和删除对象的方法,这里只增加士兵和删除士兵。

适用:

  一、你想表示对象的部分-整体层次结构。

  二、你希望客户或略组合对象和单个对象的不同,用户将统一使用组合结构中的所有对象。

Composite模式优点:

  使客户端调用简单,客户端可以一致的使用组合结构或其中单个对象,用户就不必关系自己处理的是单个对象还是整个组合结构,这就简化了客户端代码。

  更容易在组合体内加入对象部件. 客户端不必因为加入了新的对象部件而更改代码。

Composite模式的缺点:

 

结构图:

Component:为组合中的对象声明接口。在适当情况下实现所有类共有接口的缺省行为。声明一个接口用于访问和管理Component的子组件。

Leaf:在组合中表示叶节点对象,叶节点没有子节点。

composite:定义有子部件的那些部件有关的操作。存储子部件。在Component接口中实现与子部件有关的操作。

Client:通过Component接口操作组合部件的对象。

执行方式:

  用户使用Component类接口和组合结构中的对象进行交互。如果接受者是一个叶节点,则直接处理请求。如果接受者是Composite,它通常将请求发送给它的子部件,在转发请求之前或之后可能执行一些辅助操作。

示例一:

  在具体实现的时候,分队继承接口,实现添加士兵和删除士兵的方法,而士兵对象也要重载删除和添加的方法,只不过实现的是空方法!这样在外部调用的时候,士兵和分队就想同样的对象一样,可以使用它们的方法!

<UML>


<说明>

  Unit总军队是树干(基类)、army分队是树枝(组合对象)、射手和激光炮是树叶(局部对象)

<代码>

如下代码,一个军队就形成了!军队可以增加士兵同时也可以增加军队。

非组合模式
abstract class Unit {
    abstract function bombardStrength();//攻击强度
}

class Archer extends Unit {//射手
    function bombardStrength() {
        return 4;
    }
}

class LaserCanonUnit extends Unit {//激光炮
    function bombardStrength() {
        return 44;
    }
}

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 bombardStrength() {
        $ret = 0;
        foreach( $this->units as $unit ) {//遍历单个兵
            $ret += $unit->bombardStrength();
        }

        foreach( $this->armies as $army ) {//遍历军队
            $ret += $army->bombardStrength();
        }

        return $ret;
    }
}

$main_army = new Army();
$main_army->addUnit( new Archer() );
$main_army->addUnit( new LaserCanonUnit() );

$sub_army = new Army();
$sub_army->addUnit( new Archer() );
$sub_army->addUnit( new Archer() );
$sub_army->addUnit( new Archer() );
$main_army->addArmy( $sub_army );

print "attacking with strength: {$main_army->bombardStrength()}\n";

 

以上可以实现军队了,但是如果需要增加运兵船,那么久需要增加TroopCarrier类了,同样需要实现和Army一样的功能。很明显,这样做代码会变得越来越乱,但是很多都是重复的。此时就要用到组合模式了。


<实现成组合模式的代码>

class UnitException extends Exception {}

abstract class Unit {
    abstract function addUnit( Unit $unit );//加入单位对象
    abstract function removeUnit( Unit $unit );//删除单位对象
    abstract function bombardStrength();//计算攻击力
}
//在子类中实现了addUnit和removeUnit方法
class Archer extends Unit {//射手
    function addUnit( Unit $unit ) {
        throw new UnitException( get_class($this)." is a leaf" );
    }
function removeUnit( Unit $unit ) { throw new UnitException( get_class($this)." is a leaf" ); }
function bombardStrength() { return 4; } } class LaserCanonUnit extends Unit {//激光炮 function addUnit( Unit $unit ) { throw new UnitException( get_class($this)." is a leaf" ); } function removeUnit( Unit $unit ) { throw new UnitException( get_class($this)." is a leaf" ); } function bombardStrength() { return 44; } } 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 ) { $units = array(); foreach ( $this->units as $thisunit ) { if ( $unit !== $thisunit ) { $units[] = $thisunit; } } $this->units = $units; } function bombardStrength() { $ret = 0; foreach( $this->units as $unit ) { $ret += $unit->bombardStrength(); } return $ret; } } $main_army = new Army(); $main_army->addUnit( new Archer() );//增加一个射手 +4 $main_army->addUnit( new LaserCanonUnit() );//增加一个激光炮 +44 $sub_army = new Army(); $sub_army->addUnit( new Archer() );//增加一个射手 +4 $sub_army->addUnit( $test = new Archer() );//增加一个射手 +4 $sub_army->addUnit( new Archer() );//增加一个射手 +4 $sub_army->removeUnit( $test );//删除一个射手 +4 $main_army->addUnit( $sub_army ); print "attacking with strength: {$main_army->bombardStrength()}\n";

<结果>

attacking with strength: 56 

  这里的叶子节点即射手和激光炮是不需要增加和删除单位的,但是在接口中实现了增加和删除的操作,那么就要在这些不需要这些操作的类中实现这些方法了,造成了很多无用的操作。可以将类型检查放在Unit接口中,这样就避免了在叶子节点中实现addUnit()和removeUnit()方法了。

class UnitException extends Exception {}

abstract class Unit {//在Unit接口中判断是否需要addUnit和removeUnit方法
    abstract function bombardStrength();

    function addUnit( Unit $unit ) {
        throw new UnitException( get_class($this)." is a leaf" );
    }

    function removeUnit( Unit $unit ) {
        throw new UnitException( get_class($this)." is a leaf" );
    }
}

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

class LaserCanonUnit extends Unit {

    function bombardStrength() {
        return 44;
    }
}

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 ) {
        $units = array();
        foreach ( $this->units as $thisunit ) {
            if ( $unit !== $thisunit ) {
                $units[] = $thisunit;
            }
        }
        $this->units = $units;
    }

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

$main_army = new Army();

$main_army->addUnit( new Archer() );//增加一个射手 +4
$main_army->addUnit( new LaserCanonUnit() );//增加一个激光炮 +44

$sub_army = new Army();
$sub_army->addUnit( new Archer() );//增加一个射手 +4
$sub_army->addUnit( new Archer() );//增加一个射手 +4
$sub_army->addUnit( new Archer() );//增加一个射手 +4

$main_army->addUnit( $sub_army );

print "attacking with strength: {$main_army->bombardStrength()}\n";

<结果>

attacking with strength: 60 

  但是这里并没有解决问题,只是简化了一点而已。如果我们将增加和删除单元的接口后移到组合对象接口中,那么就可以轻松避免了。同时如果增加不能登上运兵船的骑兵。

class UnitException extends Exception {}

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

class Archer extends Unit {
    function bombardStrength() {
        return 4;
    }
}
class LaserCanonUnit extends Unit {
    function bombardStrength() {
        return 44;
    }
}
class Cavalry extends Unit {//骑兵
    function bombardStrength() {
        return 2;
    }
}

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

    function getComposite() {
        return $this;
    }

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

    function removeUnit( Unit $unit ) {//默认实现缺省方法
        $units = array();
        foreach ( $this->units as $thisunit ) {
            if ( $unit !== $thisunit ) {
                $units[] = $thisunit;
            }
        }
        $this->units = $units;
    }

    function addUnit( Unit $unit ) {//默认实现缺省方法
        if ( in_array( $unit, $this->units, true ) ) {
            return;
        }
        $this->units[] = $unit;
    }
}
class TroopCarrier extends CompositeUnit {//如果是运兵船,骑兵不能上船

    function addUnit( Unit $unit ) {
        if ( $unit instanceof Cavalry ) {
            throw new UnitException("Can't get a horse on the vehicle");
        }
        parent::addUnit( $unit );
    }

    function bombardStrength() {
        return 0;
    }
}
class Army extends CompositeUnit {

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

class UnitScript {
    static function joinExisting( Unit $newUnit, Unit $occupyingUnit ) {
        if ( ! is_null( $comp = $occupyingUnit->getComposite() ) ) {//判断是否是组合对象
            $comp->addUnit( $newUnit );//如果是组合对象,将$newUnit加入
        } else {
            $comp = new Army();//如果不是合并
            $comp->addUnit( $occupyingUnit );
            $comp->addUnit( $newUnit );
        }
        return $comp;
    }
}

$main_army = new Army();
$main_army->addUnit( new Archer() );
$main_army->addUnit( new LaserCanonUnit() );
print_r( $main_army );
echo "<br>";
UnitScript::joinExisting( new Archer(), $main_army );
$combined = UnitScript::joinExisting( new Archer(), new LaserCanonUnit() );
print_r( $combined );

<结果>

Army Object ( [units:CompositeUnit:private] => Array ( [0] => Archer Object ( ) [1] => LaserCanonUnit Object ( ) ) ) 
Army Object ( [units:CompositeUnit:private] => Array ( [0] => LaserCanonUnit Object ( ) [1] => Archer Object ( ) ) ) 

 

 

<示例二>:黑枣图书公司的想把图书分类以目录的形式打印出来。

<UML>

<说明>

1、抽象角色(MenuComponent),给参加组合的对象规定一系列接口。接口中包括管理节点的方法

   在本例中如addremove方法。MenuComponent中可以定义一些接口的默认动作。

2、树叶组件(Leaf)角色:在组合中表示叶节点对象,叶节点没有子节点。在组合中定义图元对象的行为。

   在本例中为MenuItem,为菜单的具体项,不可以有子项。

3、树枝组件(Composite)角色:存储子部件。定义有子部件的那些部件的行为。在Component接口中实现与子部件有关的操作。

   在本例中为Menu,为菜单项,菜单项包含了一个menuComponents数组来储存MenuItem

<代码>

abstract class MenuComponent{
    public $name;
    public abstract function getName();
public abstract function add(MenuComponent $menu); public abstract function remove(MenuComponent $menu);
public abstract function getChild($i); public abstract function show(); } class MenuItem extends MenuComponent{//菜单具体项,叶子对象 public function __construct($name){ $this->name = $name; } public function getName(){ return $this->name; } public function add(MenuComponent $menu){ return false; } public function remove(MenuComponent $menu){ return false; } public function getChild($i){ return null; } public function show(){ echo " |-".$this->getName()."\n"; } } class Menu extends MenuComponent{//菜单项,组合对象 public $menuComponents = array(); public function __construct($name){ $this->name = $name; } public function getName(){ return $this->name; } public function add(MenuComponent $menu){ $this->menuComponents[] = $menu; } public function remove(MenuComponent $menu){ $key = array_search($menu, $this->menuComponents); if($key !== false) unset($this->menuComponents[$key]); } public function getChild($i){ if(isset($this->menuComponents[$i])) return $this->menuComponents[$i]; return null; } public function show(){ echo ":" . $this->getName() . "\n"; foreach($this->menuComponents as $v){ $v->show(); } } }

 

posted on 2014-06-22 15:01  color_story  阅读(780)  评论(0编辑  收藏  举报

导航