开篇关注目录就会知道:组合是一个新的开始。在系统代码设计过程中,我们通过继承、父类、子类的方式来组织代码,本质上对应于业务的整体规范和具体需求。因此,我们需要按照某种逻辑来组合类,这样类就可以成为一个集合系统。组合模式描述了这样一种逻辑——当我们需要通过标准化的操作将一些类连接起来,甚至格式化成父子层次关系时,我们有什么模式(“工具”)可用。管理一组对象的复杂度比较高,从外部进行理论上的解释就更难了。为此,这里虚构一个场景:在之前的模式中,我们使用了一个类似于文明游戏的场景,现在继续使用,在这里,我们要实现一个简单的战斗单位组成系统。首先定义一些战斗单位类型:abstractclassUnit{abstractfunctionbombardStrength();}classArcherextendsUnit{functionbombardStrength(){return3;}}classLaserCannonUnitextendsUnit{functionbombardStrength(){return10;}}我们设计了一个抽象方法bombardStrength来设置战斗单位的伤害,通过继承实现了两个具体的子类:Archer和LaserCannonUnit。完整的类自然应该包括移动速度、防御等,但是你会发现这是Homogeneous,为了示例代码的简洁我们就省略了。接下来,我们创建一个单独的类来实现战斗单位(军队)的组合。陆军类{private$units=array();functionaddUnit(Unit$unit){array_push($this->units,$unit);}functionbombradStrength(){$ret=0;foreach($this->unitsas$unit){$ret+=$unit->bombardStrength();}}返回$ret;}}Army类的addUnit方法用于接收单位,通过bombardStrength方法计算总伤害。但是我想如果你对游戏感兴趣的话,你是不会满足于这么粗糙的模型的,我们来补充点新的:我军/联盟分裂(目前如果混合在一起,已经无法区分部队归属了)陆军类{private$units=array();私人$armies=array();functionaddUnit(Unit$unit){array_push($this->units,$unit);}functionaddArmy(Army$army){array_push($this->armies,$army);}}functionbombradStrength(){$ret=0;foreach($this->unitsas$unit){$ret+=$unit->bombardStrength();}}foreach($this->armiesas$army){$ret+=$army->bombardStrength();}返回$ret;}}那么现在,这个Army类不仅可以合并军队,还可以在需要的时候加入一个军队,盟军和我军分开。最后,我们观察写好的类,它们都有相同的方法bombardStrength,从逻辑上讲,它们也有一些共同点,所以我们可以将它们集成为一个类家族。使用单根继承实现组合模式,下面放出UML:可以看到,所有的军队类都是从Unit派生出来的,不过这里要注意:Army和TroopCarrier类是复合对象,Archer和LaserCannon类是本地对象或叶对象。这里补充说明组合模式的类结构。它是一个树结构。组合对象是树枝,可以产生数量可观的树叶。叶子对象是最小的单元,不能包含这种组合方式的其他对象。这里有一个问题:本地对象是否需要包含addUnit、removeUnit等方法,这里为了保持一致性,后面再讨论。接下来,我们开始实现Unit和Army类。观察Army,可以发现它可以保存所有从Unit派生的类实例(对象),因为它们的方法相同,需要军队的攻击强度。只要调用攻击强度方法就可以完成。概括。现在,我们面临的一个问题是:如何实现add和remove方法,一般的组合模式会在父类中添加这些方法,这样可以保证所有的派生类共享同一个接口,但同时意味着:系统设计师将容忍冗余Remain。这是默认实现:classUnitExceptionextendsException{}abstractclassUnit{abstractfunctionaddUnit(Unit$unit);抽象函数removeUnit(Unit$unit);abstractfunctionbombardStrength();}classArcherextendsUnit{functionaddUnit(Unit$unit){thrownewUnitException(get_class($this)."属于最小单元。");}functionremoveUnit(Unit$unit){thrownewUnitException(get_class($this)."属于最小单元。");}functionbombardStrength(){return3;}}classArmyextendsUnit{private$units=array();functionaddUnit(Unit$unit){如果(in_array($unit,$this->units,true)){return;}$this->units[]=$unit;}functionremoveUnit(Unit$unit){$this->units=array_udiff($this->units,array($unit),function($a,$b){return($a===$b)?0:1;});}functionbombardStrength(){$ret=0;foreach($this->unitsas$unit){$ret+=$unit->bombardStrength();}return$ret;}}我们可以做一些小改进:将add和remove的异常代码移到父类中:abstractclassUnit{functionaddUnit(Unit$unit){thrownewUnitException(get_class($this)."属于最小单元。");}functionremoveUnit(Unit$unit){thrownewUnitException(get_class($this)."属于最小单元。");}abstractfunctionbombardStrength();}classArcherextendsUnit{functionbombardStrength(){return3;}}复合模式的好处是灵活的:复合模式中的所有类共享相同的父类型,所以你可以轻松地在设计中添加新的复合对象或部分对象,而无需大量修改代码。简单:使用复合模式,客户端代码只需要设计一个简单的接口。对于客户端来说,调用所需的接口就足够了,not任何情况下“调用一个不存在的接口”,至少,它也会反馈一个异常。书中某部分的一个小操作,可能会产生很大的影响,但并不为所有人所知——例如:我们把军队1名下的一支军队(a)移动到军队2,实际上把所有的军队都移动了军队中的部队(a)军队个体。Displayarrival:树状结构可以很方便的遍历,通过树状结构的迭代可以快速得到对象所包含的信息。最后,让我们做一个小测试。//创建编号$myArmy=newArmy();//添加士兵$myArmy->addUnit(newArcher());$myArmy->addUnit(newArcher());$myArmy->addUnit(newArcher());//创建编号$subArmy=newArmy();//添加士兵$subArmy->addUnit(newArcher());$subArmy->addUnit(newArcher());$myArmy->addUnit($subArmy);echo"MyArmy的总伤害是:".$myArmy->bombardStrength();//MyArmy的总伤害是:15Effect解释一下:为什么像addUnit这样的方法一定要出现在partial类中,因为我们要保持Unit-Client的透明性,当client进行任何访问的时候,很明显必须有addUnit或者目标类中的其他方法,无需猜测。现在,我们将Unit类解析成一个抽象的子类CompositeUnit,把复合对象的方法移到它里面,并添加一个监控机制:getComposite。现在,我们已经解决了“冗余方法”,但是每次调用,我们都必须通过getComposite来确认它是否是一个复合对象,并且根据这个逻辑,我们可以写一段测试代码。完整代码:classUnitExceptionextendsException{}abstractclassUnit{functiongetComposite(){returnnull;}abstractfunctionbombardStrength();}abstractclassCompositeUnitextendsUnit{private$units=array();函数getComposite(){返回$this;}protectedfunctionunits(){return$this->units;}functionaddUnit(Unit$unit){if(in_array($unit,$this->units,true)){返回;}$this->units[]=$unit;}functionremoveUnit(Unit$unit){$this->units=array_udiff($this->units,array($unit),function($a,$b){return($a===$b)?0:1;});}}classUnitScript{staticfunctionjoinExisting(Unit$newUnit,Unit$occupyingUnit){if(!is_null($comp=$occupyingUnit->getComposite())){$comp->addUnit($newUnit);}else{$补偿=新军队();$comp->addUnit($occupyingUnit);$comp->addUnit($newUnit);}返回$comp;}}当我们需要在子类中实现个性化的业务逻辑时,组合模式的缺陷之一就显现出来了:简化的前提是所有的类都继承自同一个基类,而简化的好处有时在降低物体安全的成本。为了弥补失去的安全性,我们需要类型检查,直到有一天你会发现:我们做了太多的检查——它开始显着影响代码效率。classTroopCarrier{functionaddUnit(Unit$unit){if($unitinstanceofCavalry){thrownewUnitException("不能把马放在船上");超级::添加单位($单位);}}函数bombardStrength(){返回0;}}组合模式的优势不断被越来越多的特殊对象所抵消。只有当大部分的部分对象可以互换时,组合方式才最适用。另一个令人担忧的问题是:组合对象的操作成本。如果你玩过统帅或者横扫千军,你就会明白这个问题的严重性。当你拥有上千个作战单位,而这些单位本身还各自属于不同的编号时,你每一次计算某支军队的价值,都会带来巨大的军队开支,甚至系统崩溃。相信大家都能想到:在父对象或者最高层对象中,保存一个缓存,这样的解决方案,但实际上除非你使用精度极高的浮点数,否则要注意缓存的有效性(尤其是像JS这种语言,为了做一系列的游戏值缓存,我忽略了它的值转换错误)。最后,对象持久化需要注意:1.复合模式虽然是一种优雅的模式,但是它不能轻易将自身存储在关系型数据库中。您需要通过多次昂贵的查询将整个结构保存在关系数据库中。在数据库中;2、我们可以通过分配一个ID来解决1.的问题,但是我们在获取到对象之后还是需要重建父子引用关系,这样会有点混乱。总结如果你想“像操作一个对象一样为所欲为”,那么组合模式就是你所需要的。然而,复合模式依赖于组件的简单性,随着我们引入复杂的规则,代码变得越来越难以维护。奖励:复合模式不能很好地存储在关系数据库中,但非常适合在XML中持久化。(persistence=preservation)(parentclass=superclass,因为英文是SuperClass,extra,你可能喜欢“直接继承”和“间接继承”的概念)小疑惑我发现老外经常用实际应用来教学,特别非常游戏之类有趣的应用,不知道这是国外教学的传统,还是我理解错了。
