命令模式:把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。命令模式允许请 求的一方和发送的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否执行,何时被执行以及是怎么被执 行的。系统支持命令的撤消。
主要用于企业应用设计中,特别促进了控制器(请求和分发处理)和领域模型(应用逻辑)的分离。页面中需要处理很多不同的任务,就应该考虑将任务进行封装。
命令模式由3部分组成:实例化命令对象的客户端、部署命令对象的调用者、接受命令对象的接受者。
一、下面是一个简单的例子:创建可被保存或传递的命令对象,这里的大于0判断和偶数判断都是被封装在了对象中间,这样讲对象引入,就可以执行这里的过滤命令的操作了。还是不太明白这里的用处,吼吼,后面看看在哪用!
<UML>
命令模式由3部分组成:
Command(命令):在一个方法调用之上定义一个抽象;
ConcreteCommand(具体的命令):一个操作的实现;(即上图具体的Receiver)
Invoker(调用者):引用Command实例作为它可用的操作。
<示例一>
interface Validator {//命令对象接口 public function isValid($value); } /** * 具体的命令对象 */ class MoreThanZeroValidator implements Validator {//大于0 public function isValid($value) { return $value > 0; } } class EvenValidator implements Validator {//偶数 public function isValid($value) { return $value % 2 == 0; } } /** * 调用者 */ class ArrayProcessor { protected $_rule; public function __construct (Validator $rule) { $this->_rule = $rule; } public function process(array $numbers) { foreach ($numbers as $n) { if ($this->_rule->IsValid($n)) { echo $n, " "; } } } } // Client code $processor = new ArrayProcessor(new MoreThanZeroValidator()); $processor->process(array(1, 20, 18, 5, 0, 31, 42));
<结果>
1 20 18 5 31 42
<示例二>
/** * 命令模式:将一个请求封装为一个对象从而使你可用不同的请求对客户进行参数化,对请求排除或记录请求日志,以及支持可取消的操作 * * 命令对象 */ interface Command{ public function execute(); } class ConcreteCommand implements Command{//具体的命令对象 private $_receiver; public function __construct($receiver){ $this->_receiver = $receiver;//命令的接受者 } public function execute(){ $this->_receiver->action(); } } class ConcreteCommand1 implements Command{ private $_receiver; public function __construct($receiver){ $this->_receiver = $receiver; } public function execute(){ $this->_receiver->action1(); } } class ConcreteCommand2 implements Command{ private $_receiver; public function __construct($receiver){ $this->_receiver = $receiver; } public function execute(){ $this->_receiver->action2(); } } /** * Receiver:接受命令对象 */ class Receiver{ private $_name = null; public function __construct($name) { $this->_name = $name; } public function action(){ echo $this->_name."CS<br/>"; } public function action1(){ echo $this->_name."DOTA<br/>"; } public function action2(){ echo $this->_name."BASKETBALL<br/>"; } } /** * Invoker:调用命令对象 */ class Invoker{ private $_command = array(); public function setCommand($command) { $this->_command[] = $command; } public function executeCommand(){//执行命令 foreach($this->_command as $command){ $command->execute(); } } public function removeCommand($command){//删除命令 $key = array_search($command, $this->_command); if($key !== false){ unset($this->_command[$key]); } } } //接受者 $objRecevier = new Receiver("No.1"); $objRecevier1 = new Receiver("No.2"); $objRecevier2 = new Receiver("No.3"); //命令对象 $objCommand = new ConcreteCommand($objRecevier); $objCommand1 = new ConcreteCommand1($objRecevier); $objCommand2 = new ConcreteCommand2($objRecevier); $objCommand3 = new ConcreteCommand1($objRecevier1); $objCommand4 = new ConcreteCommand2($objRecevier2); // 使用 Recevier的两个方法 $objInvoker = new Invoker(); $objInvoker->setCommand($objCommand); $objInvoker->setCommand($objCommand1); $objInvoker->setCommand($objCommand2); $objInvoker->executeCommand(); $objInvoker->removeCommand($objCommand1); $objInvoker->executeCommand(); $objInvoker->setCommand($objCommand2); $objInvoker->setCommand($objCommand3); $objInvoker->setCommand($objCommand4); $objInvoker->executeCommand();
<示例三>上面只是简单地介绍了命令对象的逻辑,但是还有很多可以完善的地方:
命令类中处理了运算的逻辑,简单的逻辑还好,但是如果过于复杂,那么就要引入对应的对象做处理,做重构处理,职责分明。
调用者类中通过构造方法__construct()来调用指定的命令对象,如果可以,在web请求中根据请求的内容直接实例化好需要的对象是不是更好呢?这样的调用者只要知道在初始化的过程中引入请求的内容就可以了,同时将实例化的工作交给工厂来执行。
抽象的命令类:
abstract class Command(){ abstract function execute(CommandContext $context); }
为什么要使用抽象类而不使用接口呢?因为在适当的时候抽象类中可以定时公用函数用于子类继承。
具体的抽象类:
class LoginCommand Extands Command{ function execute(CommandContext $context){ //这里是单例模式得到的对象,登录的逻辑操作在这里 $manager = Registry::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; } }
提交反馈信息:
class FeedbackCommand Extands Command{ function execute(CommandContext $context){ //这里是单例模式得到的对象,反馈信息的逻辑操作在这里 $msgSystem = Registry::getMessageSystem(); $email= $context->get('email'); $msg= $context->get('msg'); $topic= $context->get('topic'); $result= $msgSystem->send($email,$msg,$topic); if(!$result){//失败 $context->setError($msgSystem->getError()); return false; } return true; } }
这里,我们的命令类中,减少具体的逻辑操作,而放到具体的注册表模式声明的执行类中间。execute()调用的直接是请求的内容对象。
内容CommandContext机制,请求的数据可以被传递给Command对象,同时响应也可以被返回给视图,即错误时返回的false。
CommandContext类:关联数据包装而成的对象。
class CommandContext{ private $params = array(); private $error = ""; function __construct(){ $this->params = $_REQUEST; } function addParam($key,$val){ $this->params[$key] = $val; } function get($key){ return $this->params[$key]; } function setError($error){ $this->error = $error; } function getError(){ return $this->error; } }
这里的请求数组不是通过命令对象直接引入的,而是在调用者中初始化,获取具体的请求内容,然后更具请求决定初始化具体的命令类。
class Controller{//调用命令的类 private $context; function __construct(){//初始化请求 $this->context = new CommandContext(); } function getContext(){ return $this->context; } function process(){ //实例化具体的命令类 $cmd = CommandFactory::getCommand($this->context->get('action')); if(!$cmd ->execute($this->context)){ //处理失败 }else{ //成功,现在分发视图 } } }
根据请求实例化具体的命令类的工厂方法:
class CommandNotFoundException extends Exception{} class CommandFactory{ private static $dir = 'commands';//路径 //根据请求获得类,并实例化类返回实例化对象 static function getCommand($action='Default'){ if(prge_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 find '$file'"); } require_once($file); if(!class_exists($class)){ throw new CommandNotFoundException ("No '$class' "); } $cmd = new $class(); return $cmd; } }
执行过程:
$controller = new Controller(); $context = $conntroller->getContext();//获得请求内容 $context->addParam('action','login');//$params['action']=login $context->addParam('username','bob'); $context->addParam('pass','tiddles'); $context->process();