您的位置:首页 > 其它

命令模式(Command Action 事务模式 对象行为模式)

2017-07-24 00:00 330 查看

意图

将命令写成类
命令模式是从界面设计中提取出来的一种分离耦合,提高重用性的方法,可以分离显示逻辑和业务逻辑的耦合。
将一个请求封装成一个对象,从而使你可以用不同的请求对客户进行参数化;对请求队列或记录请求日志,以及支持可撤销的操作。

适用性

抽象出执行的动作以参数化某对象。你可用过程语言中的回调函数表达这种参数化机制。所谓回调函数是指函数现在某处注册,而它将在稍后某个需要的时候被调用。Command模式是回调机制的一个面向对象的替代品。

在不同的时刻指定、排列和执行请求。一个Command对象可以有一个与初始请求无关的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负责请求的命令对象传送给另一个不同的进程并在那儿实现该请求。

支持取消操作。Command的Excute操作可实施操作前将状态存储起来,在取消该操作时这个状态用来消除该操作的影响。Command接口必须添加一个Unexecute操作,该操作取消上一次Execute调用的效果。执行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表并分别调用Unexecute和Execute来实现重数不限的“取消”和“重做”。

支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。在Command接口中添加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。从崩溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用Execute操作重新执行它们。

用构建在原语操作上的高层操作构造一个系统。这样一种结构在支持事务的信息系统中很常见。一个事务封装了对数据的一组变动。Command模式提供了对事务进行建模的方法。Command有一个公共的接口,使得你可以用同一种方式调用所有的事务。同时使用该模式也易于添加新事务以扩展系统。

结构



参与者

Command

1.声明执行操作的接口

ConcreteCommand

将一个接收者对象绑定于一个动作

调用接收者相应的操作,以实现Execute(以实现命令角色声明的执行操作接口)

Client

1.创建一个具体命令对象并设定它的接收者

Invoker

1.要求该命令执行这个请求(调用命令对象执行这个请求)

Receiver

1.知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者。

代码

Command

public interface Command {
public void execute();
}

ConcreteCommand

public class DrawCommand implements Command {
private Drawable drawable;
private String position;
public DrawCommand(Drawable drawable,String position){
this.drawable = drawable;
this.position = position;
}
public void execute() {
drawable.draw(position);
}
}
public class MacroCommand implements Command {
private Stack<Command> commands = new Stack<Command>();
public void execute() {
for(Command command:commands){
command.execute();
}
}
public void append(Command command){
if(command!=null){
commands.push(command);
}
}
public void undo(){
if(!commands.isEmpty()){
commands.pop();
}
}
public void clear(){
commands.clear();
}
}

Receiver

public interface Drawable {
public void draw(String position);
}
public class DrawCanvas implements Drawable {
public void draw(String position) {
System.out.println("=======DrawCanvas======"+position);
}
}

Client

public class Main {
public static void main(String[] args) {
MacroCommand macroCommand = new MacroCommand();
macroCommand.append(new DrawCommand(new DrawCanvas(),"hello"));
macroCommand.append(new DrawCommand(new DrawCanvas(),"world"));
macroCommand.append(new DrawCommand(new DrawCanvas(),"124"));
macroCommand.append(new DrawCommand(new DrawCanvas(),"456"));
macroCommand.execute();
}
}

协作

Client创建一个ConcreteCommand对象并指定它的Receiver对象。

某Invoker对象存储该ConcreteCommand对象

该Invoker通过调用Command对象的Execute操作来提交一个请求。若该命令是可撤销的,ConcreteCommand就在执行Execute操作之前存储当前状态以用于取消该命令

ConcreteCommand对象对它的Receiver的一些操作以执行该请求。



上图展示了这些对象之间的交互。它说明了Command是如何将调用者和接收者(以及它执行的请求)解耦的。

优点

Command模式将调用操作的请求对象与知道如何实现该操作的接收对象解耦。

Command是头等的对象。它们可像其他的对象一样被操纵和扩展。

具体命令角色可以被不同的请求角色重用

你可将多个命令装配成一个复合命令。一般来说,复合命令时Composite模式的一个实例。

增加新的Command很容易,因为者无需改变已有的类。

缺点

使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。

实现

一个命令对象应达到何种智能程度

命令对象的能力可大可小。一个极端是它仅确定一个接收者和执行该请求的动作。另一个极端是它自己实现所有功能,根本不需要额外的接收者对象。当需要定义与已有的类无关的命令,当没有合适的接收者,或当一个命令隐式地知道它的接收者时,可以使用后一极端方式。例如,创建另一个应用窗口的命令对象本身可能和任何其他的对象一样有能力创建该窗口。在这两个极端间的情况是命令对象有足够的信息可以动态的找到它们的接收者。

支持取消(undo)和重做(redo)

如果Command提供方法逆转(reverse)它们操作的执行,就可支持取消和重做功能。为达到这个目的,ConcreteCommand类可能需要存储额外的状态信息。这个状态包括:
1. 接收者对象,它真正执行处理该请求的各操作。
2. 接收者上执行操作的参数
3. 如果处理请求的操作会改变接收者对象中的某些值,那么这些值必须先存储起来。接收者还必须提供一些操作,以使该命令可将接收者恢复到它先前的状态。
若应用只支持一次取消操作,那么只需存储最近一次被执行的命令。而若要支持多级的取消和重做,就需要有一个已被执行命令的历史列表,该表列的最大长度决定了取消和重做的次数。历史表列存储了已被执行的命令序列。向后遍历该表列并逆向执行(reverse-executing)命令是取消它们的结果;向前遍历并执行命令是重执行它们。
有时可能不得不将一个可撤销的命令在它可以被放入列表中之前先拷贝下来。这是因为执行原来的请求的命令对象将在稍后执行其他的请求。如果命令的状态在各次调用之间会发生变化,那就必须进行拷贝以区分相同命令的不同调用。
例如,一个删除选定对象的删除命令在它每次被执行时,必须存储不同的对象集合。因此该删除命令对象在执行后必须被拷贝,并且将该拷贝放入历史表列中。如果该命令的状态在执行时从不改变,则不需要拷贝,而仅需将一个对该命令的引用放入历史表列中。在放入历史表列中之前必须被拷贝的那些Command起着原型的作用。

避免取消操作过程中的错误积累

在实现一个可靠的、能保持原先语义的取消/重做机制时,可能会遇到滞后影响问题。由于命令重复的执行、取消执行,和重执行的过程可能会积累错误,以致一个应用的状态最终偏离初始值。这就有必要在Command中存入更多的信息来保证这些对象可被精确地复原成它们的初始状态。这里可使用Memento模式来让该Command访问这些信息而不暴露其他对象的内部信息。

经典例子

Swing、Struts、页面设计中的各种按钮功能

相关模式

Composite Pattern

在宏命令(macro command)时,有时会用到组合模式。

Memento Pattern

在存储Command参与者的记录时,有时会用到Memento Pattern。用来保持某个状态,命令用这一状态来取消它的效果。

Prototype Pattern

预复制已发生的事件(产生的命令)时,有时会用到Prototype Pattern。在被放入历史表列前必须被拷贝的命令起到一种原型的作用

敬请期待“解释器模式(Interpreter Pattern,类行为型模式)”
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: