问题:
使用组合模式开发时,每增加一个操作,就需要在局部类和组合类的各个子类中增加对于新操作的支持,这不但会使类变得越来越臃肿,而且每次都需要对多个类的代码进行修改。
概念:
访问者模式,表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
实现:
//单个对象基类
abstract class Unit
{
protected $depth;
//判断是否为对象集合,从而决定是否支持addUnit()和removeUnit()操作
public function getComposite()
{
return null;
}
public function accept(ArmyVisitor $visitor)
{
$method = 'visit' . get_class($this);
$visitor->$method($this);
}
protected function setDepth($depth)
{
$this->depth = $depth;
}
public function getDepth()
{
return $this->depth;
}
abstract public function bombardStrength();
}
//对象组合基类
abstract class CompositeUnit extends Unit
{
private $units = [];
public function getComposite()
{
return $this;
}
protected function units()
{
return $this->units;
}
public function removeUnit(Unit $unit)
{
$this->units = array_udiff($this->units, array($unit),
function($a, $b) { return $a == $b ? 0 : 1; });
}
public function addUnit(Unit $unit)
{
if(in_array(($unit), $this->units, true)) {
return;
}
$unit->setDepth($this->depth + 1);
$this->units[] = $unit;
}
public function accept(ArmyVisitor $visitor)
{
parent::accept($visitor);
foreach($this->units as $unit) {
$unit->accept($visitor);
}
}
}
//单个对象具体实现
class LaserCanon extends Unit
{
public function bombardStrength()
{
return 44;
}
}
//对象组合具体实现
class TroopCarrier extends CompositeUnit
{
public function bombardStrength()
{
$ret = 0;
foreach($this->units() as $unit) {
$ret += $unit->bombardStrength();
}
return $ret;
}
}
class Army extends CompositeUnit
{
public function bombardStrength()
{
$ret = 0;
foreach($this->units() as $unit) {
$ret += $unit->bombardStrength();
}
return $ret;
}
}
//访问者基类
abstract class ArmyVisitor
{
//默认的访问方法
abstract public function visit(Unit $node);
public function visitLaserCanon(LaserCanon $node)
{
$this->visit($node);
}
public function visitTroopCarrier(TroopCarrier $node)
{
$this->visit($node);
}
public function visitArmy(Army $node)
{
$this->visit($node);
}
}
//具体访问者实现
class TextDumpArmyVisitor extends ArmyVisitor
{
private $text = '';
public function visit(Unit $node)
{
$ret = '';
$pad = 4 * $node->getDepth();
$ret .= sprintf("%{$pad}s", '');
$ret .= get_class($node).':';
$ret .= 'bombard:'.$node->bombardStrength().'<br>';
$this->text .= $ret;
}
public function getText()
{
return $this->text;
}
}
//应用
$army = new Army();
$army->addUnit(new LaserCanon());
$army->addUnit(new TroopCarrier());
$army->addUnit(new Army());
$textDump = new TextDumpArmyVisitor();
$army->accept($textDump);
echo $textDump->getText();
效果:
优点:
1. 访问者模式把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。
2. 增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者。访问者模式将有关的行为集中到一个访问者对象中。
3. 每增加一个操作,只需在基类中增加一个调用访问者对应方法的方法,无需对多个类进行修改,然后在观察者中实现基于多个类的多个操作方法即可。实质上就是将多个类的操作方法抽取出来放到访问者实现。
缺点:
外部化操作可能会破坏封装。需要在类中为访问者提供额外方法。