命令模式.docx
《命令模式.docx》由会员分享,可在线阅读,更多相关《命令模式.docx(16页珍藏版)》请在冰豆网上搜索。
![命令模式.docx](https://file1.bdocx.com/fileroot1/2023-1/23/0e465d31-a64c-430c-ae33-b521e74072a5/0e465d31-a64c-430c-ae33-b521e74072a51.gif)
命令模式
php
/**
*命令模式
*将一个请求封装为一个对象从而使你可用不同的请求对客户进行参数化,对请求排除或记录请求日志,以及支持可取消的操作
*/
interfaceCommand{
publicfunctionexecute();//抽象命令角色
}
classInvoker{//请求者(Invoker)角色
private$_command=array();
publicfunctionsetCommand($command){
$this->_command[]=$command;
}
publicfunctionexecuteCommand(){
foreach($this->_commandas$command){
$command->execute();
}
}
publicfunctionremoveCommand($command){
$key=array_search($command,$this->_command);
if($key!
==false){
unset($this->_command[$key]);
}
}
}
classReceiver{//接收者(Receiver)角色
private$_name=null;
publicfunction__construct($name){
$this->_name=$name;
}
publicfunctionaction(){
echo$this->_name."action
";
}
publicfunctionaction1(){
echo$this->_name."action1
";
}
}
classConcreteCommandimplementsCommand{//具体命令(ConcreteCommand)角色
private$_receiver;
publicfunction__construct($receiver){
$this->_receiver=$receiver;
}
publicfunctionexecute(){
$this->_receiver->action();
}
}
classConcreteCommand1implementsCommand{//具体命令(ConcreteCommand)角色
private$_receiver;
publicfunction__construct($receiver){
$this->_receiver=$receiver;
}
publicfunctionexecute(){
$this->_receiver->action1();
}
}
classConcreteCommand2implementsCommand{//具体命令(ConcreteCommand)角色
private$_receiver;
publicfunction__construct($receiver){
$this->_receiver=$receiver;
}
publicfunctionexecute(){
$this->_receiver->action();
$this->_receiver->action1();
}
}
$objRecevier=newReceiver("No.1");
$objRecevier1=newReceiver("No.2");
$objRecevier2=newReceiver("No.3");
$objCommand=newConcreteCommand($objRecevier);
$objCommand1=newConcreteCommand1($objRecevier);
$objCommand2=newConcreteCommand($objRecevier1);
$objCommand3=newConcreteCommand1($objRecevier1);
$objCommand4=newConcreteCommand2($objRecevier2);
$objInvoker=newInvoker();
$objInvoker->setCommand($objCommand);
$objInvoker->setCommand($objCommand1);
$objInvoker->executeCommand();
---------------------------------------------------------------------------------------
$objInvoker->removeCommand($objCommand1);
$objInvoker->executeCommand();
----------------------------------------------------------------------------------------
$objInvoker->setCommand($objCommand2);
$objInvoker->setCommand($objCommand3);
$objInvoker->setCommand($objCommand4);
$objInvoker->executeCommand();
结果:
No.1action
No.1action1
-------------------------------
No.1action
-------------------------------
No.1action
No.2action
No.2action1
No.3action
No.3action1
命令模式涉及到五个角色,它们分别是:
● 客户端(Client)角色:
创建一个具体命令(ConcreteCommand)对象并确定其接收者。
● 抽象命令(Command)角色:
声明了一个给所有具体命令类的抽象接口。
● 具体命令(ConcreteCommand)角色:
定义一个接收者和行为之间的弱耦合;实现execute()方法,负责调用接收者的相应操作。
execute()方法通常叫做执行方法。
● 请求者(Invoker)角色:
负责调用命令对象执行请求,相关的方法叫做行动方法。
● 接收者(Receiver)角色:
负责具体实施和执行一个请求。
任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。
模式分析
1.命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。
2.每一个命令都是一个操作:
请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作。
3.命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
4.命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
5.命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。
模式优点
1.降低对象之间的耦合度。
2.新的命令可以很容易地加入到系统中。
3.可以比较容易地设计一个组合命令。
4.调用同一方法实现不同的功能
模式缺点
使用命令模式可能会导致某些系统有过多的具体命令类。
因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
缺点:
如果产品太多,会导致命令大量增多。
造成代码量增多,有点和工厂方法模式比较相像
最后说一下命令模式的缺点,那就是命令如果很多,开发起来就要头疼了。
特别是很多简单的命令,实现起来就几行代码的事,而使用命令模式的话,不用管命令多简单,都需要写一个命令类来封装。
适用环境
1.系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
2.系统需要在不同的时间指定请求、将请求排队和执行请求。
3.系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
4.系统需要将一组操作组合在一起,即支持宏命令。
命令模式的优点
● 更松散的耦合
命令模式使得发起命令的对象——客户端,和具体实现命令的对象——接收者对象完全解耦,也就是说发起命令的对象完全不知道具体实现对象是谁,也不知道如何实现。
● 更动态的控制
命令模式把请求封装起来,可以动态地对它进行参数化、队列化和日志化等操作,从而使得系统更灵活。
● 很自然的复合命令
命令模式中的命令对象能够很容易地组合成复合命令,也就是宏命令,从而使系统操作更简单,功能更强大。
● 更好的扩展性
由于发起命令的对象和具体的实现完全解耦,因此扩展新的命令就很容易,只需要实现新的命令对象,然后在装配的时候,把具体的实现对象设置到命令对象中,然后就可以使用这个命令对象,已有的实现完全不用变化。
命令模式总结:
①它能很容易的维护所有命令的集合。
②它可以很方便的实现撤销和恢复命令。
③可以很方便的将每个执行记录日志。
④最重要的就是将发起者与实现者分离。
命令模式有以下优点:
1.命令模式将调用操作的请求对象与知道如何实现该操作的接收对象解耦。
2.具体命令角色可以被不同的请求者角色重用。
3.你可将多个命令装配成一个复合命令。
4.增加新的具体命令角色很容易,因为这无需改变已有的类。
命令模式的以下适用环境:
1.需要抽象出待执行的动作,然后以参数的形式提供出来——类似于过程设计中的回调机制。
而命令模式正是回调机制的一个面向对象的替代品。
2.在不同的时刻指定、排列和执行请求。
一个命令对象可以有与初始请求无关的生存期。
3需要支持取消操作。
4.支持修改日志功能。
这样当系统崩溃时,这些修改可以被重做一遍。
5.需要支持事务操作。
Command模式通常可应用到以下场景:
1Multi-levelundo(多级undo操作)
如果系统需要实现多级回退操作,这时如果所有用户的操作都以command对象的形式实现,系统可以简单地用stack来保存最近执行的命令,如果用户需要执行undo操作,系统只需简单地popup一个最近的command对象然后执行它的undo()方法既可。
2Transactionalbehavior(原子事务行为)
借助command模式,可以简单地实现一个具有原子事务的行为。
当一个事务失败时,往往需要回退到执行前的状态,可以借助command对象保存这种状态,简单地处理回退操作。
3Progressbars(状态条)
假如系统需要按顺序执行一系列的命令操作,如果每个command对象都提供一个
getEstimatedDuration()方法,那么系统可以简单地评估执行状态并显示出合适的状态条。
4Wizards(导航)
通常一个使用多个wizard页面来共同完成一个简单动作。
一个自然的方法是使用一个command对象来封装wizard过程,该command对象在第一个wizard页面显示时被创建,每个wizard页面接收用户输入并设置到该command对象中,当最后一个wizard页面用户按下“Finish”按钮时,可以简单地触发一个事件调用execute()方法执行整个动作。
通过这种方法,command类不包含任何跟用户界面有关的代码,可以分离用户界面与具体的处理逻辑。
5GUIbuttonsandmenuitems(GUI按钮与菜单条等等)---软件工具视图
Swing系统里,用户可以通过工具条按钮,菜单按钮执行命令,可以用command对象来封装命令的执行。
6Threadpools(线程池)
通常一个典型的线程池实现类可能有一个名为addTask()的public方法,用来添加一项工作任务到任务队列中。
该任务队列中的所有任务可以用command对象来封装,通常这些command对象会实现一个通用的接口比如java.lang.Runnable。
7Macrorecording(宏纪录)
可以用command对象来封装用户的一个操作,这样系统可以简单通过队列保存一系列的command对象的状态就可以记录用户的连续操作。
这样通过执行队列中的command对象,就可以完成"Playback"操作了。
8Networking
通过网络发送command命令到其他机器上运行。
9ParallelProcessing(并发处理)
当一个调用共享某个资源并被多个线程并发处理时。
11.5命令模式
近几年,我的每个项目几乎都用到了命令模式。
命令模式最初来源于图形化用户界面设计,
但现在广泛应用于企业应用设计,特别促进了控制器(请求和分发处理)和领域模型(应用逻辑)的分离。
说得更简单一点,命令模式有助于系统更好地进行组织,并易于扩展。
11.5.1问题
所有系统都必须决定如何响应用户请求。
在PHP中,这个决策过程通常是由分散的各个PHP
页面来处理。
比如当用户访问一个PHP页面(如feedback.php)时,用户明确地告诉系统他所要求的功能和接口。
但现在PHP开发者日益倾向于在设计系统时采用单一入口的方式(我们将在下一章中讨论)。
无论是多个入口还是单个入口,接收者都必然将用户请求委托给一个更加关注于应用逻辑的层来进行处理。
这个委托在用户请求不同页面时尤为重要。
如果没有委托,代码重复将会不可避免地蔓延在整个项目中。
让我们想象一下,假设一个有很多任务要执行的项目,需要允许某些用户登录,某些用户可
以提交反馈。
我们可以分别创建login.php和feedback.php页面来处理这些任务,并实例化专门的类以完成任务。
不过遗憾的是,系统中的用户界面很难被精确地一一对应到系统任务。
比如我们可能要求每个页面都有登录和反馈的功能。
如果页面必须处理很多不同的任务,就应该考虑将任务进行封装。
封装之后,向系统增加新任务就会变得简单,并且可以将系统中的各部分分离开来。
当然,这时我们可以使用命令模式。
11.5.2实现
命令对象的接口极为简单,因为它只要求实现一个方法execute()。
在图11-8中,Command被定义为一个抽象类。
同样简单地,它也可以被定义为接口。
我喜欢将它定义为抽象类,因为有时基类也可以为它的衍生对象提供有用的公共功能。
命令模式由3部分组成:
实例化命令对象的客户端(client)、部署命令对象的调用者(invoker)和接受命令的接收者(receiver)。
通过客户端,接收者可以在命令对象的构造方法中被传递给命令对象,或者通过某种工厂对
象被获得。
相对而言,我更喜欢后一种办法,它可以保持构造方法参数清晰明了,而且所有的Command对象都可以用完全相同的方式实例化。
让我们创建一个具体的Command类:
abstractclassCommand{
abstractfunctionexecute(CommandContext$context);
}
classLoginCommandextendsCommand{
functionexecute(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());
returnfalse;
}
$context->addParam("user",$user_obj);
returntrue;
}
}
LoginCommand被设计为与AccessManager(访问管理器)对象一起工作。
AccessManager是一个虚构出来的类,它的任务就是处理用户登录系统的具体细节。
注意Contend:
:
execute()
方法要求使用CommandContext对象作为参数《J2EE核心模式》中将其描述为RequestHelper。
通过CommandContext机制,请求数据可被传递给Command对象,同时响应也可以被返回到视图层。
以这种方式使用对象是很有好处的,因为我们可以不破坏接口就把不同的参数传递给命令对象。
从本质上说,CommandContext只是将关联数组变量包装而成的对象,但我们仍会经常扩展它来执行额外的任务。
下面是一个简单的CommandContext实现:
classCommandContext{
private$params=array();
private$error="";
function__construct(){
$this->params=$_REQUEST;
functionaddParam($key,$val){
$this->params[$key]=$val;
}
functionget($key){
return$this->params[$key];
}
functionsetError($error){
$this->error=$error;
}
functiongetError(){
return$this->error;
}
}
}
因此通过使用CommandContext对象,LoginCommand能够访问请求数据:
提交的用户名和密码。
我们使用了一个简单的类Registry,它带有用于生成通用对象的静态方法,可以返回LogicCommand所需要的AccessManager对象。
如果AccessManager报告一个错误,则LoginCommand保存错误信息到CommandContext对象中以供表现层使用并返回false。
如果一切正常,LoginCommand只返回true。
注意command对象不应该执行太多的逻辑。
它们应该负责检查输入、处理错误、缓存对象和调用其他对象来执行一些必要的操作。
如果你发现应用逻辑过多地出现在Command类中,通常需要考虑重构代码。
这样的代码会导致代码重复,因为它们不可避免地会在不同的Command类中被复制粘贴。
你至少需要考虑这些应用逻辑的功能应该属于哪部分代码。
最好把这样的代码迁移到业务对象中或者放入一个外观层中。
现在我们仍然缺少客户端代码(即用于创建命令对象的类)及调用者类(使用生成的命令的类)。
在一个Web项目中,选择实例化哪个命令对象的最简单的办法是根据请求本身的参数来决定。
下面是一个简化的客户端代码:
classCommandNotFoundExceptionextendsException{}
classCommandFactory{
privatestatic$dir="commands";
staticfunctiongetCommand($action='Default'){
if(preg_match('/\W/',$action)){
thrownewException("illegalcharactersinaction");
}
$class=UCFirst(strtolower($action))."commands";
$file=self:
:
$dir.DIRECTOR_SEPARATOR."{$class}.php";
if(!
file_exists($file)){
thrownewCommandNotFoundException("couldnotfind'$file'");
}
require_once($file);
if(!
class_exists($class)){
thrownewCommandNotFoundException("not'$class'classlocated");
}
$cmd=new$class();
return$cmd;
}
}
CommandFacotry类在commands目录里查找特定的类文件。
文件名是通过CommandContext
对象的$action参数来构造的,该参数是从请求中被传到系统中的。
如果文件和类都存在,那么会返回命令对象给调用者。
我们可以在这里添加更多的错误检查,比如保证找到的类是Command类的子类,保证构造方法没有参数等,但目前的版本对我们来说已经足够说明问题。
这种方式的优点是你可以随时将新的Command类添加到commands目录下,然后系统便立即支持它了。
下面是一个简单的调用者:
classController{
private$context;
function__construct(){
$this->context=newCommandContext();
}
functiongetContext(){
return$this->context;
}
functionprocess(){
$cmd=CommandFactory:
:
getCommand($this->context->get('action'));
if(!
$cmd->execute($this->context)){
//处理失败
}else{
//成功
//现在分发视图
}
}
}
$controller=newController();
//伪造用户请求
$context=$controller->getContext();
$context->addParam('action','login');
$context->addParam('username','bob');
$context->addParam('pass','tzddles');
$controller->process();
19.php
php
abstractclassCommand{
abstractfunctionexecute(CommandContext$context);
}
classLoginCommandextendsCommand{//命令接收者
functionexecute(CommandContext$context){
$manager=Registry:
:
getAccessManager();
$user=$co