【深入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) 中,这里面会产生很多调用:

  1. 首先会产生一个 TextDumpArmyVisitor::visitArmy() 方法,这个方法会产生一次调用 TextDumpArmyVisitor::visit() 方法;
  2. 然后会进入 foreach 循环,依次调用:
    TextDumpArmyVisitor::visitArcher() 方法,这个方法产生一次调用 TextDumpArmyVisitor::visit() 方法;
    TextDumpArmyVisitor::visitLaserCannonUnit() 方法,这个方法产生一次调用TextDumpArmyVisitor::visit() 方法;
    TextDumpArmyVisitor::visitCavalry() 方法,这个方法产生一次调用 TextDumpArmyVisitor::visit() 方法;
  3. 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

这里写图片描述