命令模式(学习笔记)

  1. 意图

  将请求转换为一个包含与请求相关的全部信息的独立对象该转换让你能根据不一样的请求将方法参数化延迟请求执行或将其放入队列中且能实现可撤销操做java

  2. 动机

  假如开发一款新的文字编辑器当前的任务是建立一个包含多个按钮的工具栏并让每一个按钮对应编辑器的不一样操做建立了一个很是简洁的按钮它不只可用于生成工具栏上的按钮还可用于生成各类对话框的通用按钮。尽管全部按钮看上去都很类似 但它们能够完成不一样的操做 (打开 保存 打印和应用等 问题是在哪里放置这些按钮的点击处理代码呢 最简单的解决方案是在使用按钮的每一个地方都建立大量的子类 这些子类中包含按钮点击后必须执行的代码算法

             

   可是这种方式有严重的缺陷首先建立了大量的子类当每次修改基类按钮都有可能须要修改全部子类的代码简单来讲GUI 代码以一种拙劣的方式依赖于业务逻辑中的不稳定代码(违背了依赖倒置原则)。更棘手的是,复制/粘贴文字等操做可能会在多个地方被调用例如用户能够点击工具栏上小小的 “复制 按钮或者经过上下文菜单复制一些内容又或者直接使用键盘上的 Ctrl+C 。咱们的程序最初只有工具栏所以可使用按钮子类来实现各类不一样操做换句话来讲复制按钮Copy­Button子类包含复制文字的代码是可行的在实现了上下文菜单快捷方式和其余功能后要么须要将操做代码复制进许多个类中要么须要让菜单依赖于按钮然后者是更糟糕的选择
数据库

  优秀的软件设计一般会将变化的部分进行封装而这每每会致使软件的分层最多见的例子一层负责用户图像界面另外一层负责业务逻辑GUI 层负责在屏幕上渲染美观的图形捕获全部输入并显示用户和程序工做的结果当须要完成一些重要内容时(好比计算月球轨道或撰写年度报告GUI 层则会将工做委派给业务逻辑底层。在代码中就是,一个 GUI 对象传递一些参数来调用一个业务逻辑对象这个过程一般被描述为一个对象发送请求给另外一个对象。
网络

  命令模式建议 GUI 对象不直接提交这些请求 应该将请求的全部细节 (例如调用的对象 方法名称和参数列表 抽取出来组成命令类 该类中仅包含一个用于触发请求的方法。GUI 对象触发命令便可命令对象会自行处理全部细节工做。全部命令实现相同的接口该接口一般只有一个没有任何参数的执行方法让你能在不和具体命令类耦合的状况下使用同一请求发送者执行不一样命令此外还有额外的好处如今你能在运行时切换链接至发送者的命令对象以此改变发送者的行为编辑器

                   

  3. 适用性

  • 若是须要经过操做来参数化对象,可使用命令模式

  命令模式可将特定的方法调用转化为独立对象。故而能够将命令做为方法的参数进行传递将命令保存在其余对象中或者在运行时切换已链接的命令等ide

  • 若是想要将操做放入队列中或者远程执行操做可以使用命令模式

  同其余对象同样命令也能够实现序列化(序列化的意思是转化为字符串从而能方便地写入文件或数据库中一段时间后该字符串可被恢复成为最初的命令对象所以你能够延迟或计划命令的执行但其功能远不止如此使用一样的方式你还能够将命令放入队列记录命令或者经过网络发送命令函数

  • 若是你想要实现操做回滚功能 可以使用命令模式  

  尽管有不少方法能够实现撤销和恢复功能但命令模式多是其中最经常使用的一种为了可以回滚操做你须要实现已执行操做的历史记录功能命令历史记录是一种包含全部已执行命令对象及其相关程序状态备份的栈结构这种方法有两个缺点:工具

  首先程序状态的保存功能并不容易实现由于部分状态多是私有的你可使用备忘录模式来在必定程度上解决这个问题this

  其次备份状态可能会占用大量内存所以,有时你须要借助另外一种实现方式命令无需恢复原始状态而是执行反向操做反向操做也有代价它可能会很难甚至是没法实现spa

  • 支持修改日志,这样在系统崩溃时,修改能够被重作一遍。在command接口中添加装载操做和存储操做,能够用来保持一个一致的修改日志。从崩溃中恢复的过程包括从磁盘中从新读入记录下的命令并用Execute操做从新执行它们
  • 用构建在原语操做上的高层操做构建一个系统。这样一种结构在支持事物的信息系统中很常见。一个事务封装了对数据的一组变更。Command模式提供了对事务进行建模的方法。Command有一个公共接口,使得你能够用同一种方式调用全部的事务。同时,使用该模式也易于添加新事务以扩展系统

  4. 结构

      

  5. 效果

  1. Command模式将调用操做的对象与知道如何实现该操做的对象解耦(单一职责原则)

  2. 实现撤销和恢复功能

  3. 实现操做的延迟执行

  4. 能够将多个命令装配成一个组合命令。通常来讲,组合命令是Composite模式的一个实例

  5. 能够在不修改客户端代码的状况下,在程序中建立新的命令(开闭原则)  

  6. 代码实现  

  commands/Command.java: 抽象基础命令

package command.commands;

import command.editor.Editor;

/**
 * @author GaoMing
 * @date 2021/7/25 - 20:05
 */
public abstract class Command {
    public Editor editor;
    private String backup;

    Command(Editor editor) {
        this.editor = editor;
    }

    void backup() {
        backup = editor.textField.getText();
    }

    public void undo() {
        editor.textField.setText(backup);
    }

    public abstract boolean execute();
}

  commands/CopyCommand.java: 将所选文字复制到剪贴板

package command.commands;

import command.editor.Editor;

/**
 * @author GaoMing
 * @date 2021/7/25 - 20:06
 */
public class CopyCommand extends Command {
    public CopyCommand(Editor editor) {
        super(editor);
    }

    @Override
    public boolean execute() {
        editor.clipboard = editor.textField.getSelectedText();
        return false;
    }

}

  commands/PasteCommand.java: 从剪贴板粘贴文字

package command.commands;

import command.editor.Editor;

/**
 * @author GaoMing
 * @date 2021/7/25 - 20:06
 */
public class PasteCommand extends Command{
    public PasteCommand(Editor editor) {
        super(editor);
    }

    @Override
    public boolean execute() {
        if (editor.clipboard == null || editor.clipboard.isEmpty()) return false;

        backup();
        editor.textField.insert(editor.clipboard, editor.textField.getCaretPosition());
        return true;
    }
}

  commands/CutCommand.java: 将文字剪切到剪贴板

package command.commands;

import command.editor.Editor;

/**
 * @author GaoMing
 * @date 2021/7/25 - 20:06
 */
public class CutCommand extends Command{
    public CutCommand(Editor editor) {
        super(editor);
    }

    @Override
    public boolean execute() {
        if (editor.textField.getSelectedText().isEmpty()) return false;

        backup();
        String source = editor.textField.getText();
        editor.clipboard = editor.textField.getSelectedText();
        editor.textField.setText(cutString(source));
        return true;
    }

    private String cutString(String source) {
        String start = source.substring(0, editor.textField.getSelectionStart());
        String end = source.substring(editor.textField.getSelectionEnd());
        return start + end;
    }
}

  commands/CommandHistory.java: 命令历史

package command.commands;

import java.util.Stack;

/**
 * @author GaoMing
 * @date 2021/7/25 - 20:06
 */
public class CommandHistory {
    private Stack<Command> history = new Stack<>();

    public void push(Command c) {
        history.push(c);
    }

    public Command pop() {
        return history.pop();
    }

    public boolean isEmpty() { return history.isEmpty(); }
}

  editor/Editor.java: 文字编辑器的 GUI

package command.editor;

import command.commands.*;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * @author GaoMing
 * @date 2021/7/25 - 20:06
 */
public class Editor {
    public JTextArea textField;
    public String clipboard;
    private CommandHistory history = new CommandHistory();

    public void init() {
        JFrame frame = new JFrame("Text editor (type & use buttons, Luke!)");
        JPanel content = new JPanel();
        frame.setContentPane(content);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
        textField = new JTextArea();
        textField.setLineWrap(true);
        content.add(textField);
        JPanel buttons = new JPanel(new FlowLayout(FlowLayout.CENTER));
        JButton ctrlC = new JButton("Ctrl+C");
        JButton ctrlX = new JButton("Ctrl+X");
        JButton ctrlV = new JButton("Ctrl+V");
        JButton ctrlZ = new JButton("Ctrl+Z");
        Editor editor = this;
        ctrlC.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                executeCommand(new CopyCommand(editor));
            }
        });
        ctrlX.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                executeCommand(new CutCommand(editor));
            }
        });
        ctrlV.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                executeCommand(new PasteCommand(editor));
            }
        });
        ctrlZ.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                undo();
            }
        });
        buttons.add(ctrlC);
        buttons.add(ctrlX);
        buttons.add(ctrlV);
        buttons.add(ctrlZ);
        content.add(buttons);
        frame.setSize(450, 200);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private void executeCommand(Command command) {
        if (command.execute()) {
            history.push(command);
        }
    }

    private void undo() {
        if (history.isEmpty()) return;

        Command command = history.pop();
        if (command != null) {
            command.undo();
        }
    }
}

  Demo.java: 客户端代码

package command;

import command.editor.Editor;

/**
 * @author GaoMing
 * @date 2021/7/25 - 20:05
 */
public class Demo {
    public static void main(String[] args) {
        Editor editor = new Editor();
        editor.init();
    }
}

  运行结果

    

  7. 与其余模式的关系

  • 责任链模式、命令模式、中介者模式和观察者模式用于处理请求发送者和接收者之间的不一样链接方式:
    - 责任链按照顺序将请求动态传递给一系列的潜在接收者,直至其中一名接收者对请求进行处理
    - 命令在发送者和请求者之间创建单向链接
    - 中介者清除了发送者和请求者之间的直接链接,强制它们经过一个中介对象进行间接沟通
    - 观察者容许接收者动态地订阅或取消接收请求

  • 能够同时使用命令和备忘录模式来实现“撤销”。在这种状况下,命令用于对目标对象执行各类不一样的操做,备忘录用来保存一条命令执行前该对象的状态
  • 原型模式可用于保存命令的历史记录
  • 能够将访问者模式视为命令模式的增强版本,其对象可对不一样类的多种对象执行操做
  • 命令和策略模式看上去很像,由于二者都能经过某些行为来参数化对象。可是,它们的意图有很是大的不一样:
    - 可使用命令来将任何操做转换为对象。操做的参数将成为对象的成员变量。你能够经过转换来延迟操做的执行、将操做放入队列、保存历史命令或者向远程服务发送命令等
    - 策略一般可用于描述完成某件事的不一样方式,让你可以在同一个上下文类中切换算法

  • 责任链的管理者可以使用命令模式实现。在这种状况下,你能够对由请求表明的同一个上下文对象执行许多不一样的操做
    还有另一种实现方式,那就是请求自身就是一个命令对象。在这种状况下,你能够对由一系列不一样上下文链接而成的链执行相同的操做

  8. 已知应用  

  使用示例:命令模式在 Java 代码中很常见。大部分状况下,它被用于代替包含行为的回调函数,此外还被用于对任务进行排序和记录操做历史记录等  如下是在核心 Java 程序库中的一些示例:  java.lang.Runnable 的全部实现  javax.swing.Action 的全部实现  识别方法:命令模式能够经过抽象或接口类型(发送者)中的行为方法来识别,该类型调用另外一个不一样的抽象或接口类型(接收者)实现中的方法,该实现则是在建立时由命令模式的实现封装。

相关文章
相关标签/搜索