命令模式(Command Pattern)

来源:互联网 发布:公司outlook邮箱域名 编辑:程序博客网 时间:2024/06/05 10:55

1命令模式是一个高内聚的模式。
定义如下:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。

 

2.角色说明:
● Receive接收者角色

该角色就是干活的角色,命令传递到这里是应该被执行的。


● Command命令角色
需要执行的所有命令都在这里声明。


● Invoker调用者角色

接收到命令,并执行命令。在例子中,我(项目经理)就是这个角色。


通用类图如下:


3.对命令模式的理解

1.命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。
2.每一个命令都是一个操作:请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作。
3.命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
4.命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
5.命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。

4.适用场景

1.系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
2.系统需要在不同的时间指定请求、将请求排队和执行请求。
3.系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
4.系统需要将一组操作组合在一起,即支持宏命令。

5.模式优点
1.降低对象之间的耦合度。
2.新的命令可以很容易地加入到系统中。
3.可以比较容易地设计一个组合命令。
4.调用同一方法实现不同的功能
6.模式缺点
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。


7.代码示例


/** * 定义了一个电灯作为命令的接受(执行)者 * Created by yangjiachang on 2016/9/11. */public class Light {    public void on(){        System.out.println("    开灯");    }    public void off(){        System.out.println("    关灯");    }}
/** * 定义了一个电视机作为命令的接受(执行)者 * Created by yangjiachang on 2016/9/11. */public class TV {    public void on(){        System.out.println("    打开电视");    }    public void off(){        System.out.println("    关闭电视");    }}
/**
 * 命令接口 * Created by yangjiachang on 2016/9/11. */public interface Command {    /**     * 执行命令     */    void execute();    /**     * 撤销命令     */    void undo();}
/** * 开灯命令 * Created by yangjiachang on 2016/9/11. */public class LightOnCommand implements Command {    private Light light;    public LightOnCommand(Light light){        this.light=light;    }    @Override    public void execute() {        light.on();    }    @Override    public void undo() {        light.off();    }}
/** * 关灯命令 * Created by yangjiachang on 2016/9/11. */public class LightOffCommand implements Command {    private Light light;    public LightOffCommand(Light light){        this.light=light;    }    @Override    public void execute() {        light.off();    }    @Override    public void undo() {        light.on();    }}
/** * 开电视命令 * Created by yangjiachang on 2016/9/11. */public class TVOnCommand implements Command {    private TV tv;    public TVOnCommand(TV tv){        this.tv=tv;    }    @Override    public void execute() {        tv.on();    }    @Override    public void undo() {        tv.off();    }}
/** * 关电视命令 * Created by yangjiachang on 2016/9/11. */public class TVOffCommand implements Command {    private TV tv;    public TVOffCommand(TV tv){        this.tv=tv;    }    @Override    public void execute() {        tv.off();    }    @Override    public void undo() {        tv.on();    }}
/** * 调用者:遥控器 * 可对对电灯、电视进行开关、撤销、重做命令 * * Created by yangjiachang on 2016/9/11. */public class RemoteController {    /**     * 记录重做命令     */    private Stack<Command> redoCommands = new Stack<Command>();    /**     * 记录撤销命令     */    private Stack<Command> undoCommands = new Stack<Command>();;    public void execute(Command command){        System.out.println("执行命令");        command.execute();        undoCommands.add(command);    }    public void undo(){        System.out.println("撤销命令");        if (undoCommands.empty()){            return;        }        Command lastCommand = undoCommands.pop();        lastCommand.undo();        redoCommands.push(lastCommand);    }    public void redo(){        System.out.println("重新执行命令");        if (redoCommands.empty()){            return;        }        Command lastCommand = redoCommands.pop();        lastCommand.execute();        undoCommands.push(lastCommand);    }
public static void main(String[] args) {    RemoteController remoteController = new RemoteController();    remoteController.execute(new LightOnCommand(new Light()));    remoteController.execute(new TVOnCommand(new TV()));    remoteController.undo();    remoteController.undo();    remoteController.redo();    remoteController.redo();}
执行结果:

执行命令
    开灯
执行命令
    打开电视
撤销命令
    关闭电视
撤销命令
    关灯
重新执行命令
    开灯
重新执行命令
    打开电视


在这里例子中,invoker调用者是一个多功能遥控器,可以开关灯和电视机,当然还可以扩展很多的receiver。以电灯为例,分别将开灯和关掉的命令封装成了LightOnCommand和LightOffCommand对象,每个具体命令对象都实现了Command命令接口。在这个例子中,client使用invoker遥控器来完成具体命令execute(),还能撤销命令undo()(实际上就是反向的命令)和重新执行命令redo()。因此我们需要记录这些命令已经先后顺序,这里使用了Stack来记录。遥控器中的两个Stack,undoCommands用来来保存所有的可以取消的操作,redoCommands用来保存所有重做的操作。方法执行的时候,首先调用某个Command的Execute方法,然后将该命令对象Push到undoCommands的Stack上,待以后撤销使用:


有了以上数据结构,撤销和重做逻辑就很简单,当用户点击撤销的时候:
1.首先检查 undoCommands是否为空,如果为空,直接返回,否则继续.
2.从undoCommands中Pop出最近一次的操作对象Command对象
3.然后将该命令对象Push到redoCommands上保存以便以后重做。
4.最后Pop出来的命令对象Command的undo方法实现撤销.


和撤销类似,当用户点击重做的时候
1.首先检查redoCommands是否为空,如果为空,直接返回,否则继续.
2.从redoCommands中Pop出最近一次的操作对象Command对象
3.然后将该命令对象Push到undoCommands上保存以便以后撤销。
4.最后Pop出来的命令对象Command的execute方法实现重做.


刚刚都是以单个命令来执行的,下面举个宏命令的例子:

将上面的遥控器换成一个宏命令处理器

/** * 宏命令处理 * * Created by yangjiachang on 2016/9/11. */public class MacroCommandController {    /**     * 宏命令     */    private Command[] commands;    /**     * 重新执行宏命令     */    private Stack<Command[]> redoMacroCommand = new Stack<Command[]>();    /**     * 撤销宏命令     */    private Stack<Command[]> undoMacroCommand = new Stack<Command[]>();    public void execute(Command... commands){        System.out.println("执行宏命令");        this.commands = commands;        for (Command command : commands){            command.execute();        }        undoMacroCommand.add(commands);    }    public void undo(){        System.out.println("撤销宏命令");        if (undoMacroCommand.empty()){            return;        }        Command[] lastCommand = undoMacroCommand.pop();        for (Command command : lastCommand){            command.undo();        }        redoMacroCommand.push(lastCommand);    }    public void redo(){        System.out.println("重新执行宏命令");        if (redoMacroCommand.empty()){            return;        }        Command[] lastCommand = redoMacroCommand.pop();        for (Command command : lastCommand){            command.execute();        }        undoMacroCommand.push(lastCommand);    }}
测试方法

public static void main(String[] args) {    MacroCommandController macro = new MacroCommandController();    macro.execute(new LightOnCommand(new Light()),new TVOnCommand(new TV()));    macro.undo();    macro.redo();}
测试结果

执行宏命令
    开灯
    打开电视
撤销宏命令
    关灯
    关闭电视
重新执行宏命令
    开灯
    打开电视


8.其他例子

1.ibatis在处理不同的CRUD语句时,通过SqlCommand的类型来区别执行不同的增删改查方法。


2.消息队列:你在某一段添加命令,然后另一端的线程从队列中取出命令,调用他的execute方法,等待这个调用完成,然后将此命令对象丢弃,再取下一个命令……

3.日志请求:某些应用需要将所有的动作记录在日志(磁盘)中,并能在系统死机之后,重新加载并成批地依次调用这些命令对象,从而恢复到之前的状态。



1 0
原创粉丝点击