【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),给参加组合的对象规定一系列接口。接口中包括管理节点的方法
在本例中如add及remove方法。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) 编辑 收藏 举报