【深入PHP 面向对象】读书笔记(八)
来源:互联网 发布:python可以建网站吗 编辑:程序博客网 时间:2024/05/25 05:36
【简介】组合比继承具有更大的灵活性。
组合模式:将一组对象组合为可像单个对象一样被使用的结构。
装饰模式:通过在运行时合并对象来扩展功能呢的一种灵活机制。
外观模式:为复杂或多变的系统创建一个简单的接口。
10.1 组合模式
组合模式可以很好地聚合和管理许多相似的对象,有助于我们为集合和组件之间的关系建立模型。
10.1.1 问题
管理一组对象是很复杂的,当对象中可能还包含着它们自己的对象时尤其如此。
我们以一款「文明」的游戏为基础设计一个系统,玩家可以在一个由大量区块所组成的地图上移动战斗单元。独立的单元可被组合起来一起移动、战斗和防守。我们先来定义战斗单元的类(Unit类)。
/* 战斗单元的抽象类 有一个抽象方法bombardStrength()方法用于设置战斗单元的工具强度 */abstract class Unit { abstract function bombardStrength();}/* ArcherUnit(射手)类,以及该类的攻击强度 */class ArcherUnit extends Unit { function bombardStrength() { return 1; }}/* LaserCannonUnit(激光炮)类,以及该类的攻击强度 */class LaserCannonUnit extends Unit { function bombardStrength() { return 2; }}
Unit 类定义了一个抽象方法 bombardStrength(),用于设置战斗单元对邻近区块的攻击强度。然后在 ArcherUnit(射手)和LaserCannonUnit(激光炮)类都继承了 Unit 类,并各自实现了 bombardStrength() 方法用于定义自己的攻击强度。
现在我们可以定义一个独立的类来组合战斗单元(Army类)。
/* 战斗集合的类 */class Army { private $units = array(); /* 用于接收Unit对象,并将对象保存在$units数组中 */ function addUnit(Unit $unit) { array_push($this->units, $unit); } /* 计算出总的工具强度 */ function bombardStrength() { $ret = 0; foreach ($this->units as $unit) { $ret += $unit->bombardStrength(); } return $ret; }}
Army(军队)类中定义一个 addUnit() 方法用于接收 Unit 对象。Unit 对象被保存在 $units 数组中。同时通过bombardStrength() 方法计算出总的攻击强度。
如果 Army(军队)类需要和其他Army类进行合并,同时,每个 Army 都有自己的 ID,这样 Army 在以后还可以从整编中解散出来。我们修改原来的 Army(军队)类,使其同时支持 Unit(单元)和 Army(军队)。
/* 战斗集合的类 */class Army { private $units = array(); private $armies = array(); /* 用于接收Unit对象,并将对象保存在$units数组中 */ function addUnit(Unit $unit) { array_push($this->units, $unit); } /* 用于接收Army对象,并将对象保存在$armies数组中 */ 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; }}
完成了增加 Army(军队)的功能后,后续可以对defensiveStength() 和 movementRange() 等方法做和 bombardStrength() 相似的处理,这个游戏就会变得越来越完善。
但现在有一个新的需求,需要支持运兵船可以支持最多10个战斗单元以改进它们在某些地形上的活动范围。那么按照我们之前 Army(军队)类的处理方式,也可以创建一个 TroopCarrier 类来处理运兵船的需求,因为这也是一个战斗单元组合的需求。
我们发现,无论是 Unit 类,还是 Army 类或者TroopCarrier 类,所需要的功能是一样的:它们都要移动、攻击和防守,也要提供添加和移除其他对象的功能(方法)。这些相似性会给我们一个启发:这些容器对象(Unit、Army、TroopCarrier )与它们包含的对象(ArcherUnit、LaserCannonUnit)共享一个接口(该接口中有移动、攻击bombardStrength和防御,还有添加和移除其他对象的功能)。
10.1.2 实现
组合模式定义了一个继承体系,使具有不同职责的集合可以并肩工作。组合模式中的类必须支持一个共同的操作集。我们来看「文明」游戏问题的组合模式。
模型中所有的类都扩展自 Unit 类,同时,Army 和 TroopCarrier 类被设计成了组合对象,用于包含 Unit 对象。
但是,我们也发现 ArcherUnit 和 LaserCannonUnit 类(称为局部对象,也称为树叶对象,因为组合模式为树形结构,组合对象为树干,单独存在的对象为树叶,树叶对象应为最小单元,其中不能包含其他对象。)不能包含其他 Unit 对象,但也实现了addUnit() 方法,这个问题我们后面讨论。
下面我们重写 Unit 抽象类:
abstract class Unit { abstract function addUnit(Unit $unit); abstract function removeUnit(Unit $unit); abstract function bombardStrength();}
可以看到,我们为所有的 Unit 对象设计了基本功能,并且使 Army 和 TroopCarrier 两个组合对象实现这些抽象方法:
class Army { private $units = array(); /* 用于接收Unit对象,并将对象保存在$units数组中 */ function addUnit(Unit $unit) { if (in_array($unit, $this->units, true)) { return; } array_push($this->units, $unit); } /* 用于移除Unit对象,使用array_udiff()获取差集实现移除数组元素 */ 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(); } foreach ($this->armies as $army) { $ret += $army->bombardStrength(); } return $ret; }}
Army 对象可以保存任何类型的 Unit 对象(包括 Army 对象本身,以及 ArcherUnit 对象和 LaserCannonUnit 这样的局部对象)。
现在我们再回过来看对于局部类不需要实现 add 和 remove 的方法的问题,我们可以对这些局部类在实现 add 和 remove 的方法的时候抛出异常。
/* 异常类 */class UnitException extends Exception {}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 1; }}
这样,我们就可以解决局部类调用 add 和 remove 的方法的问题了。为了不需要对所有局部类都进行重写 add 和 remove 的方法,我们可以在抽象类 Unit 类中即定义 add 和 remove 的方法抛出异常。
abstract class Unit { abstract function addUnit(Unit $unit); function addUnit(Unit $unit) { throw new UnitException(get_class($this)."是一个最小单元类,不能添加其他单元!"); } function removeUnit(Unit $unit) { throw new UnitException(get_class($this)."是一个最小单元类,不能添加其他单元!"); }}class Archer extends Unit { function bombardStrength() { return 1; }}
这样做可以移除局部类中的重复代码,但是同时组合类不再需要强制性地实现 add 和 remove 的方法了,这可能带来问题,这在我们后面的讨论中再进一步研究。
这里我们再来看客户端的调用:
/* 添加一个Army对象 */$main_army = new Army();/* 添加一些Unit对象 */$main_army->addUnit(new ArcherUnit());$main_army->addUnit(new LaserCannonUnit());/* 添加一个新的Army对象 */$sub_army = new Army();/* 添加一些Unit对象 */$sub_army->addUnit(new ArcherUnit());$sub_army->addUnit(new ArcherUnit());$sub_army->addUnit(new ArcherUnit());/* 把新的Army对象添加到第一个Army对象中 */$main_army->addUnit($sub_army);/* 获取army的攻击值(计算后的总攻击值) */echo "main_army的总攻击值为: ".$main_army->bombardStrength();
在这里我们发现,调用者无需关心添加的是局部对象(ArcherUnit、LaserCannonUnit),还是组合对象(sub_army),通过 addUnit() 方法就可以实现添加作战单元的需求;同时,在调用 main_army 的总攻击值时,也无需对每一个作战单元进行计算,只需要简单地调用 bombardStrength() 方法即可,因为在幕后就已经完成对每个局部对象的攻击强度的计算。
因此我们可以归纳出以下几个优点:
- 灵活:组合模式所有子类共享一个父类型,掌握了一个子类的创建形式后,就可以轻松地在设计中添加新的组合对象或者局部对象。
- 简单:客户端在调用时没有必要区分一个对象是局部对象还是组合对象;在调用一些方法(比如bombardStrength())时,也会产生一些幕后的委托调用,而无需客户端通过循环遍历的方式完成这些调用。
10.1.3 效果
在 ArcherUnit 和 LaserCannonUnit 这样的局部类中,是不需要加入 addUnit() 和 removeUnit() 这样的冗余方法的,这样做毫无意义而且给系统设计带来歧义。但组合模式的原则便是局部类和组合类具有同样的接口,所以我们不知道一个对象是否需要 addUnit() 和 removeUnit() 这两个方法。
因此,我们创建一个 CompositeUnit 的子类继承自 Unit 父类,并且删除 Unit 父类中的 add/remove 方法:
/* 删除掉原先的add/remove方法 */abstract class Unit { /* 新增一个getComposite()方法 */ function getComposite() { return null; } abstract function bombardStrength();}/* CompositeUnit 混合作战单元类 因为CompositeUnit没有实现bombardStrength()方法, 所以CompositeUnit声明为抽象类abstract */abstract class CompositeUnit extends Unit { private $units = array(); function getComposite() { return $this; } protected function getUnits() { return $this->units; } /* CompositeUnit类具有添加和删除单元的能力 */ function removerUnits(Unit $unit) { $this->units = array_udiff( $this->units, array($unit), function($a, $b) { return ($a==$b)?0:1; } ); } function addUnits(Unit $unit) { if (in_array($unit, $this->units)) { return; } array_push($this->units, $unit); }}
CompositeUnit 类继承自 Unit 类,但并没有实现 bombardStrength() 方法,所以 CompositeUnit 类也被声明为抽象的。添加的 getComposite() 方法默认返回 null,仅在 CompositeUnit 类中才返回 CompositeUnit 本身,所以,如果该方法返回一个对象,那么便可以调用它的 addUnit() 方法。
因此新的类组织形式如图:
下面是客户端调用:
class UnitScript { /* 有两个Unit类型的参数,第一个是新的Unit对象,第二个是之前的Unit对象 */ static function joinExisting (Unit $newUnit,Unit $occupyingUnit) { $comp; if (!is_null($comp = $occupyingUnit->getComposite())) { /* 如果第二个Unit对象是一个CompositeUnit对象,那么直接add */ $comp->addUnit($newUnit); } else { /* 如果第二个Unit对象不是一个CompositeUnit对象,那么创建一个Army对象,将两个Unit存入这个Army对象 */ $comp = new Army(); $comp->addUnit($occupyingUnit); $comp->addUnit($newUnit); } return $comp; }}
joinExisting() 方法有两个 Unit 类型的参数,第一个是新的 Unit 对象,第二个是之前的 Unit 对象。如果第二个 Unit 对象是一个 CompositeUnit 对象,那么将第一个 Unit 对象添加给它;如果第二个 Unit 对象不是一个 CompositeUnit 对象,那么创建一个 Army 对象,将两个 Unit 存入这个 Army 对象。
在这里我们会发现简化的前提是使所有的类都继承同一个基类,但是模型变得越复杂,就不得不进行越多的类型检查。比如有一个 Cavalry(骑兵)对象,假设游戏规定不能将一匹马放到运兵船上,在组合模式我们并没有自动化的方式来强制执行这个规则,因此,我们需要在 TroopCarrier 类中完成对 Cavalry 类型的检查:
class TroopCarrier { function addUnit(Unit $unit) { if ($unit instanceof Cavalry) { throw new UnitException("不能将骑兵带上船"); } super::addUnit($unit); } function bombardStrength() { return 3; }}
这里我们不得不使用 instanceof 来检测传给 addUnit() 方法的对象类型,特殊对象越来越多,组合模式开始渐渐显得弊大于利。
10.1.4 组合模式小结
在能够像对待单个对象一样对待组合对象的情况下,组合模式显得十分有用;但是,随着我们引入复杂的规则,代码就会变得越来越难以维护。
- 【深入PHP 面向对象】读书笔记(八)
- 【深入PHP 面向对象】读书笔记(一)
- 【深入PHP 面向对象】读书笔记(二)
- 【深入PHP 面向对象】读书笔记(三)
- 【深入PHP 面向对象】读书笔记(四)
- 【深入PHP 面向对象】读书笔记(五)
- 【深入PHP 面向对象】读书笔记(六)
- 【深入PHP 面向对象】读书笔记(七)
- 【深入PHP 面向对象】读书笔记(九)
- 【深入PHP 面向对象】读书笔记(十)
- 【深入PHP 面向对象】读书笔记(十一)
- 【深入PHP 面向对象】读书笔记(十二)
- 【深入PHP 面向对象】读书笔记(十三)
- 【深入PHP 面向对象】读书笔记(十四)
- 【深入PHP 面向对象】读书笔记(十五)
- 【深入PHP 面向对象】读书笔记(十六)
- 【深入PHP 面向对象】读书笔记(十七)
- 【深入PHP 面向对象】读书笔记(十八)
- C#--WinForm基于泛型集合动态填充下拉框
- Linux下Memcache服务器端的安装
- Java生产者与消费者模式的简单写法
- c的部分算术运算符和算术表达式:
- DES算法的实现
- 【深入PHP 面向对象】读书笔记(八)
- 数据库的锁机制
- leetcode之链表逆序翻转类-----92/206 逆序 24/25/61/143 按规则翻转 86/234 双指针分治 19/82/83/203 按规则删除
- POJ 2965--The Pilots Brothers' refrigerator
- 《java基础与案例开发详解》(四)
- Mac下Nginx安装环境配置详解
- memcached的常用命令
- git使用流程及常用命令
- Java300StudyNote(8)-快速理解JDK&JRE&JVM