设计模式-行为模式(Command):
1、 定义:
把请求封装成一个可重用的对象,对于每次请求,这些对象的行为都可以被参数化。并且支持可取消的操作。
《设计模式的有趣解释-追MM》中解释:Command—俺有一个mm家里管得特别严,没法见面,只好借助于她弟弟在我们俩之间传送信息,她对我有什么指示,就写一张纸条让她弟弟带给我。这不,她弟弟又传送过来一个command,为了感谢他,我请他吃了碗杂酱面,哪知道他说:“我同时给我姐姐三个男朋友送command,就数你最小气,才请我吃面。”
命令模式:命令模式把一个请求或者操作封装到一个对象中。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。命令模式允许请求的一方和发送的一方独 立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收 ,以及操作是否执行,何时被执行以及是怎么被执行的。系统支持命令的撤消。
这是Command模式的UML图:
2、 适用性:
JAVA中SWING的组件中对Command模式的运用非常多,像菜单(MenuItem)对象那样,抽象出待执行的动作以参数化某对象。你可用过程语言中的回调(Callback)函数表达这种参数化机制。所谓回调函数是指函数先在某处注册,而它将在稍后某个需要的时候被调用。Command 模式是回调机制的一个面向对象的替代品。
在不同的时刻指定、排列和执行请求。一个Command 对象可以有一个与初始请求无关的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负责该请求的命令对象传送给另一个不同的进程并在那儿实现该请求。
支持取消操作。Command 的Excecute操作可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响。Command 接口必须添加一个UnExcecute 操作,该操作取消上一次Excecute 调用的效果。执行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表并分别调用UnExcecute 和Excecute 来实现重数不限的“取消”和“重做”。
支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。在Command 接口中添加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。从崩溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用Excecute 操作重新执行它们。
用构建在原语操作上的高层操作构造一个系统。这样一种结构在支持事务( transaction)的信息系统中很常见。一个事务封装了对数据的一组变动。Command 模式提供了对事务进行建模的方法。Command 有一个公共的接口,使得你可以用同一种方式调用所有的事务。同时使用该模式也易于添加新事务以扩展系统。
3、 经典应用
Struts中的Action的设计就采用了这个模式:
protected ActionForward
processActionPerform(HttpServletRequest request, HttpServletResponse response, Action action, ActionForm form, ActionMapping mapping) throws IOException, ServletException {
try {
return (action.execute(mapping, form, request, response));
} catch (Exception e) {
return (processException(request, response, e, form, mapping));
}
}
Action是一个接口,每一个实现它的对象都必须实现execute方法,在执行这个函数之前,已经通过Action action = processActionCreate(request, response, mapping);得到客户端所请求的action对象。客户端通过login.do形式发送给ActionServlet,就可以调用LoginAction对象,通过search.do的形式就能调用SearchAction对象,而ActionServlet不用知道客户端发送的是什么请求。
4、 经典实现-用Command的模式实现Redo/Undo功能
设计了如下一个通用接口(封装用户请求的命令):
public Interface Command
{
//执行用户当前指令,比如(copy、paste、cut、delete)等
public void execute();
//返回上一操作
public void undo();
//返回下一操作
public void redo();
}
设计一个管理用户操作指令的接口CommandManager(管理用户所有的操作,以便支持取消操作):
public Interface CommandManager
{
//保存用户操作的指令
public void storeCommand(Command cmd);
//清除用户所有操作指令
public void clearAllCommand();
//返回用户上一操作
public void undo();
//返回用户下一操作
public void redo();
}
现在来实现一个用户命令:
public class DeleteCommand implements Command
{
public void execute()
{
/* 备份上次操作后数据数据 */
/* 执行用户新的指令(删除、粘贴等操作) */
}
public void undo()
{
/* 返回用户上次的数据 */
}
public void redo()
{
/* 返回用户下次的数据 */
}
}
这样的话我们每做一个编辑动作,就执行一个相应的command.接下来我们要考虑如何将这些执行过的命令保存下来,以实现undo/redo
public class CommandHistoryManager implements CommandManager
{
Vector undoList=new Vector();
Vector redoList=new Vector();
public void storeCommand(Command cmd)
{
undoList.add(cmd);
}
public void clearAllCommand()
{
undoList.clear();
redoList.clear();
}
public void undo()
{
if ( undoList.size() <= 0 ) return;
Command cmd = ((Command)(undoList.get(undoList.size()-1)));
cmd.undo();
undoList.remove(cmd);
redoList.add(cmd);
}
public void redo()
{
if ( redoList.size() <= 0 ) return;
Command cmd = ((Command)(redoList.get(redoList.size()-1)));
cmd.execute();
redoList.remove(cmd);
undoList.add(cmd);
}
}
通过storeCommand()方法,每次执行的command就可以保存到undoList中,假设再在我们在程序中放置了两个按钮,一个undo,一个redo.按下undo按钮,就执行CommandHistoryManager的undo()方法,undo()方法会调用undoList中保存的最后一个command的undo()方法,并将这个command再放到redoList中,最后从undoList中删除这个命令。这时如果再按redo按钮,它会调用CommandHistoryManager中的redo()方法。redo()方法会调用redoList中保存的最后一个command的execute()方法,并将这个command又存回到undoList中。这样就实现了无限次数的undo/redo功能。