【深入PHP 面向对象】读书笔记(十四)
来源:互联网 发布:stc12c5a60s2编程软件 编辑:程序博客网 时间:2024/05/16 13:59
11.4 访问者模式
11.4.1 问题
我们来看之前创建的战斗单元的游戏:
class Army extends CompositeUnit { function bombardStrength() { $ret = 0; foreach ($this->units() as $unit) { $ret += $unit->bombardStrength(); } return $ret; }}class LaserCannonUnit extends Unit { function bombardStrength() { return 10; }}
在这个游戏中,组合对象会调用它们的子对象来进行操作,而它们的子对象则会自己完成调用操作,即组合对象通过子对象完成的操作形成了自己的操作。
这个方式在操作比较容易的情况下比较容易实现,但是还有很多周边任务就会显得不那么好实现了。
比如,有一个转储叶节点的文本信息的操作,该操作会被添加到抽象类 Unit 中。
// Unit类 其余的内容省略function textDump($num=0) { $ret = ""; $pad = 4*$num; $ret .= sprintf("%{$pad}s",""); $ret .= get_class($this).": "; $ret .= "bombard: ".$this->bombardStrength(); return $ret;}
然后这个方法可以在 CompositeUnit 中被覆盖:
// CompositeUnit类 其余的内容省略function textDump($num=0) { $ret = parent::textDump($num); foreach ($this->units as $unit) { $ret .= $unit->textDump($num+1); } return $ret;}
我们可能还需要继续创建统计树种单元个数的方法、保存组件到数据库的方法和计算军队的食物消耗的方法。
虽然可以轻松遍历组合模式,但并非每个需要遍历对象树的操作都要在 Composite 接口中占据位置。因此,工作的重心便是充分利用对象结构提供的轻松遍历的优势,但同时避免类过度膨胀。
11.4.2 实现
首先在 Unit 抽象类中定义一个 accept() 方法:
// Unit类 其余的内容省略function accept(ArmyVisitor $visit) { $method = "visit".get_class($this); $visit->$method($this);}protected function setDepth($depth) { $this->depth = $depth;}function getDepth() { return $this->depth;}
accept() 方法要求一个 ArmyVisitor 对象作为参数,函数内动态定义一个我们希望调用的方法,这让我们不必在类的继承体系中每一个叶节点上实现 accept()。同时, 添加两个 getDepth() 和 setDepth() 方法,这两个方法在树中类获取和设置一个单元的深度,父单元通过 CompositeUnit::addUnit() 添加子单元时,setDepth() 方法被调用。
function addUnit(Unit $unit) { foreach ($this->units as $thisunit) { if ($unit == $thisunit) { return; } $unit->setDepth($this->depth+1); $this->units[] = $unit; }}
接下来我们在抽象的组合类中定义另一个 accept() 方法:
function accept(ArmyVisitor $visit) { $method = "visit".get_class($this); $visit->$method($this); foreach ($this->units as $thisunit) { $thisunit->accept($visit); }}
这个 accept() 方法和 Unit::accept() 方法基本一样,但是多了些内容。它根据当前类的名称构造了一个方法名称,然后通过传入的 ArmyVisitor 对象来调用对应的方法。因此,如果当前类是 Army,则该方法调用ArmyVisitor::visitArmy();如果当前类是TroopCarrier,则调用ArmyVisitor::visitTroopCarrier();依次类推。在此之后,accept() 方法会遍历所有子对象,并调用子对象的accept() 方法。
所以在子对象中,我们可以做一些去除重复性的代码:
function accept(ArmyVisitor $visitor) { /* 通过调用抽象类Unit的accept()来去重 */ parent::accept($visitor); foreach ($this->units as $thisunit) { $thisunit->accept($visitor); }}
我们还需要定义一个 ArmyVisitor 接口。
abstract class ArmyVisitor { /* 定义一个抽象的visit()方法,具体实现交给子类实现 */ abstract function visit(Unit $node) {} function visitArcher(Archer $node) { $this->visit($node); } function visitCavalry(Cavalry $node) { $this->visit($node); } function visitLaserCannonUnit(LaserCannonUnit $node) { $this->visit($node); } function visitTroopCarrierUnit(TroopCarrierUnit $node) { $this->visit($node); } function visitArmy(Army $node) { $this->visit($node); }}
现在,我们具体实现一个子类TextDumpVisitor,用于转储文本:
class TextDumpArmyVisitor extends ArmyVisitor { private $text = ""; function visit(Unit $node) { $ret = ""; $pad = 4*$node->getDepth(); $ret .= sprintf("%{$pad}s",""); $ret .= get_class($node).": "; $ret .= "bombard: ".$node->bombardStrength()."\n"; $this->text .= $ret; } function getText() { return $this->text; }}
我们通过客户端调用:
/* 创建一个Army对象,并向其中添加Unit对象 */$main_army = new Army();$main_army->addUnit(new Archer());$main_army->addUnit(new LaserCannonUnit());$main_army->addUnit(new Cavalry());/* 创建一个TextDumpArmyVisitor对象,并将其添加到Army::accept(TextDumpArmyVisitor)中,这里面会产生很多调用 首先会产生一个TextDumpArmyVisitor::visitArmy()方法,这个方法会产生一次调用TextDumpArmyVisitor::visit()方法 然后会进入foreach循环,依次调用 TextDumpArmyVisitor::visitArcher()方法,这个方法产生一次调用TextDumpArmyVisitor::visit()方法 TextDumpArmyVisitor::visitLaserCannonUnit()方法,这个方法产生一次调用TextDumpArmyVisitor::visit()方法 TextDumpArmyVisitor::visitCavalry()方法,这个方法产生一次调用TextDumpArmyVisitor::visit()方法 visit()方法最终会写入到$text变量中,最后通过echo输出,就会有四行输出内容 */$textDump = new TextDumpArmyVisitor();$main_army->accept($textDump);echo $textDump->getText();
首先创建一个 Army 对象,并向其中添加 Unit 对象,再创建一个 TextDumpArmyVisitor 对象,并将其添加到 Army::accept(TextDumpArmyVisitor) 中,这里面会产生很多调用:
- 首先会产生一个 TextDumpArmyVisitor::visitArmy() 方法,这个方法会产生一次调用 TextDumpArmyVisitor::visit() 方法;
- 然后会进入 foreach 循环,依次调用:
TextDumpArmyVisitor::visitArcher() 方法,这个方法产生一次调用 TextDumpArmyVisitor::visit() 方法;
TextDumpArmyVisitor::visitLaserCannonUnit() 方法,这个方法产生一次调用TextDumpArmyVisitor::visit() 方法;
TextDumpArmyVisitor::visitCavalry() 方法,这个方法产生一次调用 TextDumpArmyVisitor::visit() 方法; - visit() 方法最终会写入到$text变量中,最后通过echo输出,就会有四行输出内容。
以上代码最终输出成:
// 输出Army: bombard: 15 Archer: bombard: 1 LaserCannonUnit: bombard: 10 Cavalry: bombard: 4
现在我们进行新的扩展,假设军队需要缴纳税金。征税者(即访问者visitor)访问军队,并向找到的每个单位征税,不同单位的税率不同,因此,我们定义一个TaxCollectionVistior 类,用于处理对每一个单位征税不同的税。
class TaxCollectionVisitor extends ArmyVisitor { private $due=0; private $report=""; function visit(Unit $node) { $this->levy($node,1); } function visitArcher(Archer $node) { $this->levy($node,2); } function visitCavalry(Cavalry $node) { $this->levy($node,3); } function visitTroopCarrierUnit(TroopCarrierUnit $node) { $this->levy($node,5); } private function levy(Unit $unit, $amount) { $this->report .= "Tax levied for ".get_class($unit); $this->report .= ": $amount"; $this->due = $amount; } function getReport() { return $this->report; } function getTax() { return $this->due; }}
调用的方式和之前的一样:
/* 创建一个Army对象,并向其中添加Unit对象 */$main_army = new Army();$main_army->addUnit(new Archer());$main_army->addUnit(new LaserCannonUnit());$main_army->addUnit(new Cavalry());/* 创建一个TaxCollectionVisitor对象,并将其添加到Army::accept(TaxCollectionVisitor)中,这里面会产生很多调用 首先会产生一个TaxCollectionVisitor::visitArmy()方法,这个方法会产生一次调用TaxCollectionVisitor::visit()方法 然后会进入foreach循环,依次调用 TaxCollectionVisitor::visitArcher()方法,这个方法产生一次调用TaxCollectionVisitor::visit()方法 TaxCollectionVisitor::visitLaserCannonUnit()方法,这个方法产生一次调用TaxCollectionVisitor::visit()方法 TaxCollectionVisitor::visitCavalry()方法,这个方法产生一次调用TaxCollectionVisitor::visit()方法 visit()方法最终会写入到$due变量中,最后通过echo输出,就会有四行输出内容 */$taxcollector = new TaxCollectionVisitor();$main_army->accept($taxcollector);echo $taxcollector->getTax();
最终输出:
// 输出Tax levied for Army: 1Tax levied for Archer: 2Tax levied for LaserCannonUnit: 3Tax levied for Cavalry: 4
- 【深入PHP 面向对象】读书笔记(十四)
- 【深入PHP 面向对象】读书笔记(一)
- 【深入PHP 面向对象】读书笔记(二)
- 【深入PHP 面向对象】读书笔记(三)
- 【深入PHP 面向对象】读书笔记(四)
- 【深入PHP 面向对象】读书笔记(五)
- 【深入PHP 面向对象】读书笔记(六)
- 【深入PHP 面向对象】读书笔记(七)
- 【深入PHP 面向对象】读书笔记(八)
- 【深入PHP 面向对象】读书笔记(九)
- 【深入PHP 面向对象】读书笔记(十)
- 【深入PHP 面向对象】读书笔记(十一)
- 【深入PHP 面向对象】读书笔记(十二)
- 【深入PHP 面向对象】读书笔记(十三)
- 【深入PHP 面向对象】读书笔记(十五)
- 【深入PHP 面向对象】读书笔记(十六)
- 【深入PHP 面向对象】读书笔记(十七)
- 【深入PHP 面向对象】读书笔记(十八)
- 电脑问题解决_171007_1_win10笔记本插拔耳机后扬声器无声的问题
- ASCII码排序
- MysqlDump使用整理
- java中类的调用
- hdoj 6214 Smallest Minimum Cut
- 【深入PHP 面向对象】读书笔记(十四)
- 第十三课(一)、C函数
- sdo_geometry插入点和面的sql
- JAVA8
- 点击button(非submit按钮)会提交表单的解决方法
- 图说c++对象模型:内存布局详解
- DIV布局与TABLE布局对比
- nodejs代码段(七)
- oracle里的表级约束定义的优点