1、访问者模式
这里用到之前的组合模式中的战斗单元的例子,现在需要每个单元做一件事,组合单元的需要不同一点,首先想到的就是继承与重载。
但是,客户以后可能需要增加更多其他方法,这样就不得不在Unit
和CompositeUnit
中修改增加代码。但是,我们设计好的东西,给客户用,怎么能让他们随便修改呢,,
书中的例子,与其说是访问者,说成检查者更容易理解。
我把书中的例子理解成,领导来部队检查战斗单元的战斗力,并形成报告。
报告中要有组合单元的战斗力,并且附上组合单元中,各子单元的战斗力,以此类推。
<?php
abstract class Unit
{
protected $depth = 0;
/**
* 让CompositeUnit子类,实现这个方法,以得到自身
* 用来判断该子类是单兵单元还是组合单元
*
* @return null
*/
function getComposit()
{
return null;
}
// 返回该作战单元的战斗力
abstract public function bombardStrength();
/**
* 默认单兵单元接受领导(访问者)的检查吧!具体的检查方法,在访问者类里面
* 我们只接受检查,我们当然不定义检查的方法
*
* @param ArmyVisitor $visitor 访问者
*/
public function accept(ArmyVisitor $visitor)
{
$method_name = "visit" . get_class($this);
$visitor->$method_name($this);
}
public function setDepth($depth)
{
$this->depth = $depth;
}
public function getDepth()
{
return $this->depth;
}
}
abstract class CompositeUnit extends Unit
{
protected $units = [];
public function getComposit()
{
return $this;
}
public function units()
{
return $this->units;
}
public function addUnit(Unit $unit)
{
if (in_array($unit, $this->units, true)) {
return;
}
// 如果被收编,深度+1,美化输出报告,
$unit->setDepth($this->depth + 1);
$this->units[] = $unit;
}
public function removeUnit(Unit $unit)
{
$this->units = array_udiff($this->units, [$unit], function ($a, $b) {
return ($a === $b) ? 0 : 1;
});
}
/**
* 不仅该组合单元要接受检查,其内的所有单兵单元都要一一接受检查
* @param ArmyVisitor $visitor 访问者
*/
public function accept(ArmyVisitor $visitor)
{
parent::accept($visitor);
foreach ($this->units as $unit) {
$unit->accept($visitor);
}
}
}
class Archer extends Unit
{
public function bombardStrength()
{
return 4;
}
}
class LaserCannonUnit extends Unit
{
public function bombardStrength()
{
return 44;
}
}
class Cavalry extends Unit
{
public function bombardStrength()
{
return 10;
}
}
class Army extends CompositeUnit
{
public function bombardStrength()
{
$ret = 0;
foreach ($this->units as $unit) {
$ret += $unit->bombardStrength();
}
return $ret;
}
}
class TroopCarrier extends CompositeUnit
{
public function addUnit(Unit $unit)
{
if ($unit instanceof Cavalry) {
throw new Exception("Can't get a horse on the vehicle\n");
}
parent::addUnit($unit);
}
public function bombardStrength()
{
return 0;
}
}
// 现在定义一个领导(访问者)基类
abstract class ArmyVisitor
{
// 具体的检查方法,是检查卫生呢,还是检查发型呢?
abstract public function visit(Unit $node);
// 检查各个单元,,既然都一样,有必要分开吗?
public function visitArcher(Archer $node)
{
$this->visit($node);
}
public function visitCavalry(Cavalry $node)
{
$this->visit($node);
}
public function visitLaserCannonUnit(LaserCannonUnit $node)
{
$this->visit($node);
}
public function visitTroopCarrierUnit(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() . "\n";
$this->text .= $ret;
}
public function getText()
{
return $this->text;
}
}
$army = new Army();
$army->addUnit(new Archer());
$army->addUnit(new LaserCannonUnit());
$army->addUnit(new Cavalry());
$army1 = new Army();
$army1->addUnit(new Archer());
$army1->addUnit(new LaserCannonUnit());
$army->addUnit($army1);
$textdump = new TextDumpArmyVisitor();
$army->accept($textdump);
echo $textdump->getText();
输出如下:
Army: bombard: 106
Archer: bombard: 4
LaserCannonUnit: bombard: 44
Cavalry: bombard: 10
Army: bombard: 48
Archer: bombard: 4
LaserCannonUnit: bombard: 44
现在如果哪个领导要来收税,,嘿嘿,领导自己实现一个ArmyVisitor
的子类,在其中定义好相关的方法,就可以Unit::accept(新检查)
了。
2、命令模式
我不知道还有没别的用处,反正书中的例子是以用户访问不同页面,然后控制器调用不同命令(视图)来处理相关的请求。
每个命令对象(视图)都只要实现一个execute($context)
方法,然后控制器把表单中的action
传给命令工厂,工厂生产出相应的命令(视图),控制器再调用Command::execute
完成处理请求
<?php
// 这个类,随便弄的~
class Register
{
private $user = 'largezhou';
private $pass = '123';
private $errors = '';
public static function getAccessManager()
{
return new Register();
}
public function login($user, $pass)
{
if ($user == $this->user && $pass == $this->pass) {
return new User($user);
} else {
$this->errors .= "帐号或密码错误\n";
}
}
public function getError()
{
return $this->errors;
}
}
class User
{
public $user;
public function __construct($user)
{
$this->user = $user;
}
}
class CommandContext
{
private $params = [];
private $error = '';
public function __construct()
{
$this->params = $_REQUEST;
}
public function addParam($key, $val)
{
$this->params[$key] = $val;
}
public function get($key)
{
return $this->params[$key];
}
public function setError($error)
{
$this->error = $error;
}
public function getError()
{
return $this->error;
}
}
class CommandNotFoundException extends Exception
{
}
class CommandFactory
{
private static $dir = '.';
public static function getCommand($action = 'default')
{
if (preg_match("/\W/", $action)) {
throw new Exception("illegal characters in action");
}
$class = ucfirst(strtolower($action)) . "Command";
$file = self::$dir . DIRECTORY_SEPARATOR . "{$class}.php";
if (!file_exists($file)) {
throw new CommandNotFoundException("could not found '$file'");
}
require_once $file;
if (!class_exists($class)) {
throw new CommandNotFoundException("no '$class' class located");
}
$cmd = new $class();
return $cmd;
}
}
class Controller
{
private $context;
public function __construct()
{
$this->context = new CommandContext();
}
public function getContext()
{
return $this->context;
}
public function process()
{
$cmd = CommandFactory::getCommand($this->context->get('action'));
if (!$cmd->execute($this->context)) {
echo $this->context->getError();
} else {
echo "成功\n";
}
}
}
$controller = new Controller();
$context = $controller->getContext();
// 这里的action相当于表单中的action,所以这个CommandFactory相当于一个路由分发器
// 通过不同的action(其实就是地址),生成相应的Command对象(相当于视图),
// 来处理CommandContext(这里就是请求request)。
$context->addParam('action', 'login');
$context->addParam('username', 'largezhou');
$context->addParam('pass', '123');
$controller->process();
命令按照类名.php
的方式保存
<?php
// LoginCommand.php
// 这个抽象基类应该放到单独的文件中去,这里算了
abstract class Command
{
abstract public function execute(CommandContext $context);
}
class LoginCommand extends Command
{
public function execute(CommandContext $context)
{
$manager = Register::getAccessManager();
$user = $context->get('username');
$pass = $context->get('pass');
$user_obj = $manager->login($user, $pass);
if (is_null($user_obj)) {
$context->setError($manager->getError());
return false;
}
$context->addParam('user', $user_obj);
return true;
}
}
要想添加新命令(视图),只要按照规则,命名文件和类名,实现正确的execute
方法就行来,其他地方都不用改!
网友评论