【深入PHP 面向对象】读书笔记(十四) - 执行及描述任务(四) - 访问者模式

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: 1
Tax levied for Archer: 2
Tax levied for LaserCannonUnit: 3
Tax levied for Cavalry: 4

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值