命令模式(Command Pattern)

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
命令模式和单例模式是两种常见的设计模式,它们在软件工程中都有各自的作用。 **命令模式Command Pattern)**: 命令模式是一种行为设计模式,它封装了请求并使其能被延迟执行。这个模式涉及三个角色: - **抽象命令(Command)**:定义了请求的一般接口,包含了执行请求的方法。 - **具体命令(Concrete Command)**:实现了抽象命令,负责具体的执行操作。 - **客户端(Invoker)**:调用命令,不需要关心命令的内部实现。 结合命令模式,你可以创建一组命令对象,每个命令代表一个特定的操作,而客户端通过单例模式获取唯一的命令管理器,从而能够安全、统一地发送和控制这些命令的执行。 **单例模式(Singleton Pattern)**: 单例模式确保了一个类只有一个实例,并提供一个全局访问点。这在资源有限或者需要全局访问点的场景非常有用,比如数据库连接、日志记录等。 当命令模式和单例模式结合时,可能会这样应用: - 创建一个单例的命令管理器,它负责存储所有的命令对象。 - 当客户端需要执行一个操作时,通过这个单例获取命令实例,而不是每次都去创建一个新的。 - 单例模式保证了在整个应用程序生命周期内,命令管理器始终保持唯一,避免了多个实例可能导致的问题。 相关问题: 1. 在什么情况下会考虑使用命令模式和单例模式的结合? 2. 如何确保命令管理器是单例并且在多线程环境下的安全性? 3. 命令模式和工厂模式有什么区别,它们如何共同应用于系统设计?

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值