命令模式通过将请求封装成Command对象实现了请求的调用者和请求的执行者之间的解耦,并且可以很方便的实现请求排队,日志记录,命令撤销等操作。这里边比较经典的一个功能就是通过命令模式可以把已经执行的命令记录下来,轻松的实现命令的回退,重做这些操作,类似在文本编辑器中的操作。

命令模式通常有这么几个角色:命令接口,命令接口的具体实现,命令的接收者,命令的调用者

接下来我用命令模式来模拟一个文本编辑器来举例说明下,此文本编辑器支持回退功能

我把编辑器中可以进行的操作抽象成一个命令接口TextCommand,因为要支持回退,所以要让某个命令自己提供回退的功能。

package com.lyy.design.command_design.textEdit;

// 文本编辑命令接口,抽象出执行操作和回退两个方法
public interface TextCommand {
    //封装某个具体的编辑操作
    void execute();
    //当前编辑操作对应的回退功能
    void undo();
}

接下里我定义一个插入操作的具体命令实现,也就是命令的接收者

public class InsertTextCommand implements TextCommand{
    private StringBuilder stringBuilder;
    private int start;
    private String content;

    public InsertTextCommand(StringBuilder stringBuilder, int start, String content) {
        this.stringBuilder = stringBuilder;
        this.start = start;
        this.content = content;
    }

    @Override
    public void execute() {
        //执行插入
        this.stringBuilder.insert(start,content);
    }
    //回滚插入
    @Override
    public void undo() {
        this.stringBuilder.delete(start,start+content.length());
    }
}

然后再定义一个delete操作的具体命令实现,也是一个命令的接收者

public class DeleteTextCommand implements TextCommand{
    private StringBuilder stringBuilder;
    private int start;
    private int end;
    private String deleteContent;

    public DeleteTextCommand(StringBuilder stringBuilder, int start, int end) {
        this.stringBuilder = stringBuilder;
        this.start = start;
        this.end = end;
    }

    @Override
    public void execute() {
        //删除前要把被删除的内容存起来,后边回退要用
        this.deleteContent=stringBuilder.substring(start,end);
        //执行删除
        stringBuilder.delete(start,end);
    }

    @Override
    public void undo() {
        //执行回退
        stringBuilder.insert(start,deleteContent);
    }
}

然后再定义一个命令的调用者,像命令回退,日志记录这些功能其实都是通过命令调用者来具体实现的

import java.util.Stack;

public class TextCommandInvoker {
    //使用两个栈存储执行的命令
    private Stack<TextCommand> commandStack = new Stack<>();
    private Stack<TextCommand> undoCommandStack = new Stack<>();

    public void execute(TextCommand command) {
        command.execute();
        //编辑操作执行成功后把对应的命令存到commandStack中
        commandStack.push(command);
    }
    //回退刚刚进行的操作
    public void undo(){
        if(commandStack.isEmpty()) {
            return;
        }
        //从commandStack中取出刚刚执行过的命令
        TextCommand command = commandStack.pop();
        //执行此命令的回滚操作
        command.undo();
        //把刚刚回退的命令放到undoCommandStack,等待后续的redo操作使用
        undoCommandStack.push(command);
    }
    //重做刚刚被回退的命令
    public void redo(){
        if(undoCommandStack.isEmpty()) {
            return;
        }
        //从undoCommandStack中取出刚刚被回退的命令
        TextCommand command = undoCommandStack.pop();
        //重新执行此命令
        command.execute();
        //把重新执行的命令放到commandStack,等待后续undo时调用
        commandStack.push(command);
    }
}

然后测试下我们这个命令模式的代码

public class TextClient {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        TextCommandInvoker invoker = new TextCommandInvoker();
        invoker.execute(new InsertTextCommand(sb,0,"hello"));
        System.out.println("after insert:"+sb);
        invoker.execute(new DeleteTextCommand(sb,1,5));
        System.out.println("after delete:"+sb);
        invoker.undo();
        System.out.println("after undo:"+sb);
        invoker.redo();
        System.out.println("after redo:"+sb);
    }
}

控制台会得到这样的输入

after insert:hello
after delete:h
after undo:hello
after redo:h

符合我们设计的编辑器中的编辑,回退,重做功能设想。这就是命令模式的一个典型的例子。