• 设计模式之命令模式


    定义

    将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
    可以类比现实生活中我们使用电视遥控器开关机,或者去餐厅吃饭向服务员点餐的过程,用户不需要知道点的菜是具体哪个厨师做的,
    厨师也不需要知道这个菜是哪个用户点的,命令发送者和执行者之间解耦。

    结构

    • Command,命令接口,定义执行的方法。
    • ConcreteCommand,具体命令,拥有接收者对象,调用接收者的功能来完成命令要执行的操作。
    • Receiver,接收者,真正执行命令的对象。
    • Invoker,调用者,是请求的发送者,通常会拥有很多命令对象,并通过访问命令对象来执行相关请求。
    • Client,客户端,也可以称为装配者,组装命令对象和接收者,并触发执行。

    以用户餐厅点餐为例,用户就是客户端,服务员就是调用者,点餐就是命令,厨师就是接收者。

    简单实现

    命令接口

    public interface Command {
    
      void execute();
    }
    

    具体命令

    public class ConcreteCommand implements Command {
    
      private Receiver receiver;
    
      public ConcreteCommand(Receiver receiver) {
        this.receiver = receiver;
      }
    
      @Override
      public void execute() {
        receiver.action();
      }
    }
    

    接收者

    /**
     * 命令接收者
     */
    public class Receiver {
    
      public void action() {
        System.out.println("Receiver the command and execute");
      }
    }
    

    调用者

    public class Invoker {
    
      private Command command;
    
      public Invoker(Command command) {
        this.command = command;
      }
    
      public void runCommand() {
        command.execute();
      }
    }
    

    客户端

    public class Client {
    
      public static void main(String[] args) {
        //组装命令和执行者
        Receiver receiver = new Receiver();
        Command command = new ConcreteCommand(receiver);
        Invoker invoker = new Invoker(command);
        invoker.runCommand();
      }
    
    }
    

    命令的撤销和恢复

    命令模式的关键之处就是将请求封装成对象,也就是命令对象,并定义了统一的执行操作的接口,这个命令对象可以被存储,转发,记录,处理,撤销等,
    整个命令模式都是围绕这个对象在进行。这里我们模拟实现一个支持撤销和恢复的简单文本编辑器,类似EditPlus或Word的撤销和恢复功能。

    有两种思路来实现这种撤销功能

    • 一种是补偿式,又称反操作式,比如被撤销的操作是添加,撤销就是删除。
    • 另一种是存储恢复式,将操作前的状态记录下来,撤销的时候直接恢复回去就可以了。关于这种方式,我们学习到备忘录模式时再详解。

    这里我们使用第一种方式实现撤销功能。

    /**
     * 命令接收者
     */
    public class Receiver {
    
      /**
       * 文本内容
       */
      private String textContent = "";
    
      /**
       * 文本追加
       */
      public void append(String target) {
        System.out.println("操作前内容:" + textContent);
        textContent = textContent.concat(target);
        System.out.println("操作后内容:" + textContent);
      }
    
      /**
       * 文本删除
       */
      public void remove(String target) {
        System.out.println("操作前内容:" + textContent);
        if (textContent.endsWith(target)) {
          textContent = textContent.substring(0, textContent.length() - target.length());
        }
        System.out.println("操作后内容:" + textContent);
      }
    }
    

    命令接口

    public interface Command {
    
      /**
       * 命令执行
       */
      void execute();
    
      /**
       * 命令撤销
       */
      void undo();
    
    }
    

    文本追加命令

    public class AppendCommand implements Command {
    
      private Receiver receiver;
      private String target;
    
      public AppendCommand(Receiver receiver, String target) {
        this.receiver = receiver;
        this.target = target;
      }
    
      @Override
      public void execute() {
        receiver.append(target);
      }
    
      @Override
      public void undo() {
        receiver.remove(target);
      }
    }
    

    文本删除命令

    public class RemoveCommand implements Command {
    
      private Receiver receiver;
      private String target;
    
      public RemoveCommand(Receiver receiver, String target) {
        this.receiver = receiver;
        this.target = target;
      }
    
      @Override
      public void execute() {
        receiver.remove(target);
      }
    
      @Override
      public void undo() {
        receiver.append(target);
      }
    }
    

    文本编辑器(调用者),内部保存命令执行的历史记录(可撤销的列表)和撤销执行的历史记录(可恢复的列表),有撤销才会有恢复,
    所以在执行撤销的时候向可恢复列表添加命令。撤销和恢复都是最后执行的要先撤销和恢复,所以使用栈存储。

    import java.util.Stack;
    
    /**
     * 文本编辑器,支持撤销和恢复
     */
    public class TextEditor {
    
      private Command command;
      //操作的历史记录
      private Stack<Command> undoStack = new Stack<>();
      //撤销的历史记录
      private Stack<Command> redoStack = new Stack<>();
    
      public void setCommand(Command command) {
        this.command = command;
      }
    
      public void editText() {
        command.execute();
        undoStack.push(command);
      }
    
      /**
       * 撤销功能
       */
      public void undoText() {
        if (!undoStack.isEmpty()) {
          Command command = undoStack.pop();
          command.undo();
          redoStack.push(command);
        }
      }
    
      /**
       * 恢复功能
       */
      public void redoText() {
        if (!redoStack.isEmpty()) {
          Command command = redoStack.pop();
          command.execute();
          undoStack.push(command);
        }
      }
    }
    

    客户端

    public class Client {
    
      public static void main(String[] args) {
        //组装命令和执行者
        Receiver receiver = new Receiver();
        TextEditor textEditor = new TextEditor();
        //追加hello
        textEditor.setCommand(new AppendCommand(receiver, "hello"));
        textEditor.editText();
        //追加world
        textEditor.setCommand(new AppendCommand(receiver, "world"));
        textEditor.editText();
        //删除orld
        textEditor.setCommand(new RemoveCommand(receiver, "orld"));
        textEditor.editText();
        //撤销
        textEditor.undoText();
        //撤销
        textEditor.undoText();
        //恢复
        textEditor.redoText();
      }
    
    }
    

    输出为

    操作前内容:
    操作后内容:hello
    操作前内容:hello
    操作后内容:helloworld
    操作前内容:helloworld
    操作后内容:hellow
    操作前内容:hellow
    操作后内容:helloworld
    操作前内容:helloworld
    操作后内容:hello
    操作前内容:hello
    操作后内容:helloworld
    

    结果符合预期

    宏命令

    简单来说就是包含多个命令的命令,在餐厅点餐中,用户所有点的菜组成的菜单就是一个宏命令,每一道菜都是一个命令,类似于组合模式,这里就不实现了。

    简化的命令模式

    在实际开发中,我们可以简化命令模式,将具体命令和接收者合二为一,调用者也不需要持有命令对象了,直接通过方法参数传递过来,
    将具体命令类的实现改成匿名内部类实现,这个时候的命令模式基本上等同于java回调机制的实现。

    public interface Command {
    
      void execute();
    }
    
    /**
     * 调用者
     */
    public class Invoker {
    
      public void runCommand(Command command) {
        command.execute();
      }
    }
    
    public class Client {
    
      public static void main(String[] args) {
        Invoker invoker = new Invoker();
        invoker.runCommand(() -> {
          System.out.println("Concrete Command execute");
        });
      }
    
    }
    

    命令模式在JDK的实现

    jdk中线程池ThreadPoolExecutor的实现

    import java.util.concurrent.LinkedBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    public class Client {
    
      public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>());
        executor.execute(() -> {
          System.out.println("test execute");
        });
        executor.shutdown();
      }
    
    }
    

    ThreadPoolExecutor可以看做Invoker调用者,Runnable就是Command接口,将来不及执行的请求放到队列中,这可以看做命令模式中的请求队列化操作。

    总结

    优点

    1. 更松散的耦合,命令的发起者和命令的执行者相互之间不需要知道对方。
    2. 更动态的控制,可以动态的对命令对象进行队列化,日志化,撤销等操作。
    3. 很容易组成复合命令,也就是宏命令,使系统操作更简单,功能更强大。
    4. 更好的扩展性,很容易增加新的命令对象。

    缺点

    1. 可能会导致创建过多的具体命令类。

    本质

    命令模式的本质是封装请求,封装为请求就可以进行撤销,队列化,宏命令等处理了。

    使用场景

    1. 如果需要在不同的时刻排队执行请求,可以使用命令模式,将请求封装成命令对象并队列化。
    2. 如果需要支持撤销操作。

    参考

    大战设计模式【8】—— 命令模式
    设计模式的征途—19.命令(Command)模式
    设计模式(十五)——命令模式(Spring框架的JdbcTemplate源码分析)
    命令模式(详解版)
    设计模式——命令模式
    研磨设计模式-书籍

  • 相关阅读:
    python练习:http协议介绍
    python练习(-)
    字符集与字符编码的强化理解与操作实践
    jquery设置select选中的文本
    盘点互联网巨头奉献的十大开源安全工具[转]
    $.ajax()函数
    sql事务
    json操作工具-LitJson
    接收图片二进制流并保存图片
    用Linq取两个数组的差集
  • 原文地址:https://www.cnblogs.com/strongmore/p/15170495.html
Copyright © 2020-2023  润新知