将请求转换为一个包含与请求相关的全部信息的独立对象。该转换让你能根据不一样的请求将方法参数化、延迟请求执行或将其放入队列中,且能实现可撤销操做java
假如开发一款新的文字编辑器,当前的任务是建立一个包含多个按钮的工具栏,并让每一个按钮对应编辑器的不一样操做。建立了一个很是简洁的按钮
类,它不只可用于生成工具栏上的按钮,还可用于生成各类对话框的通用按钮。尽管全部按钮看上去都很类似, 但它们能够完成不一样的操做 (打开、 保存、 打印和应用等)。 问题是在哪里放置这些按钮的点击处理代码呢? 最简单的解决方案是在使用按钮的每一个地方都建立大量的子类。 这些子类中包含按钮点击后必须执行的代码。算法
可是这种方式有严重的缺陷。首先,建立了大量的子类,当每次修改基类按钮时,都有可能须要修改全部子类的代码。简单来讲,GUI 代码以一种拙劣的方式依赖于业务逻辑中的不稳定代码(违背了依赖倒置原则)。更棘手的是,复制/粘贴文字等操做可能会在多个地方被调用。例如用户能够点击工具栏上小小的 “复制” 按钮,或者经过上下文菜单复制一些内容,又或者直接使用键盘上的 Ctrl+C
。咱们的程序最初只有工具栏,所以可使用按钮子类来实现各类不一样操做。换句话来讲,复制按钮
CopyButton子类包含复制文字的代码是可行的。在实现了上下文菜单、快捷方式和其余功能后,要么须要将操做代码复制进许多个类中,要么须要让菜单依赖于按钮,然后者是更糟糕的选择
数据库
优秀的软件设计一般会将变化的部分进行封装,而这每每会致使软件的分层。最多见的例子:一层负责用户图像界面;另外一层负责业务逻辑。GUI 层负责在屏幕上渲染美观的图形,捕获全部输入并显示用户和程序工做的结果。当须要完成一些重要内容时(好比计算月球轨道或撰写年度报告),GUI 层则会将工做委派给业务逻辑底层。在代码中就是,一个 GUI 对象传递一些参数来调用一个业务逻辑对象。这个过程一般被描述为一个对象发送请求给另外一个对象。
网络
命令模式建议 GUI 对象不直接提交这些请求。 应该将请求的全部细节 (例如调用的对象、 方法名称和参数列表) 抽取出来组成命令类, 该类中仅包含一个用于触发请求的方法。GUI 对象触发命令便可,命令对象会自行处理全部细节工做。全部命令实现相同的接口。该接口一般只有一个没有任何参数的执行方法,让你能在不和具体命令类耦合的状况下使用同一请求发送者执行不一样命令。此外还有额外的好处,如今你能在运行时切换链接至发送者的命令对象,以此改变发送者的行为。编辑器
命令模式可将特定的方法调用转化为独立对象。故而能够将命令做为方法的参数进行传递、将命令保存在其余对象中,或者在运行时切换已链接的命令等。ide
同其余对象同样,命令也能够实现序列化(序列化的意思是转化为字符串),从而能方便地写入文件或数据库中。一段时间后,该字符串可被恢复成为最初的命令对象。所以,你能够延迟或计划命令的执行。但其功能远不止如此!使用一样的方式,你还能够将命令放入队列、记录命令或者经过网络发送命令函数
尽管有不少方法能够实现撤销和恢复功能,但命令模式多是其中最经常使用的一种。为了可以回滚操做,你须要实现已执行操做的历史记录功能。命令历史记录是一种包含全部已执行命令对象及其相关程序状态备份的栈结构。这种方法有两个缺点:工具
首先,程序状态的保存功能并不容易实现,由于部分状态多是私有的。你可使用备忘录模式来在必定程度上解决这个问题。this
其次,备份状态可能会占用大量内存。所以,有时你须要借助另外一种实现方式:命令无需恢复原始状态,而是执行反向操做。反向操做也有代价:它可能会很难甚至是没法实现spa
1. Command模式将调用操做的对象与知道如何实现该操做的对象解耦(单一职责原则)
2. 实现撤销和恢复功能
3. 实现操做的延迟执行
4. 能够将多个命令装配成一个组合命令。通常来讲,组合命令是Composite模式的一个实例
5. 能够在不修改客户端代码的状况下,在程序中建立新的命令(开闭原则)
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(); } }
运行结果
责任链模式、命令模式、中介者模式和观察者模式用于处理请求发送者和接收者之间的不一样链接方式:
- 责任链按照顺序将请求动态传递给一系列的潜在接收者,直至其中一名接收者对请求进行处理
- 命令在发送者和请求者之间创建单向链接
- 中介者清除了发送者和请求者之间的直接链接,强制它们经过一个中介对象进行间接沟通
- 观察者容许接收者动态地订阅或取消接收请求
命令和策略模式看上去很像,由于二者都能经过某些行为来参数化对象。可是,它们的意图有很是大的不一样:
- 可使用命令来将任何操做转换为对象。操做的参数将成为对象的成员变量。你能够经过转换来延迟操做的执行、将操做放入队列、保存历史命令或者向远程服务发送命令等
- 策略一般可用于描述完成某件事的不一样方式,让你可以在同一个上下文类中切换算法
责任链的管理者可以使用命令模式实现。在这种状况下,你能够对由请求表明的同一个上下文对象执行许多不一样的操做
还有另一种实现方式,那就是请求自身就是一个命令对象。在这种状况下,你能够对由一系列不一样上下文链接而成的链执行相同的操做
使用示例:命令模式在 Java 代码中很常见。大部分状况下,它被用于代替包含行为的回调函数,此外还被用于对任务进行排序和记录操做历史记录等 如下是在核心 Java 程序库中的一些示例: java.lang.Runnable 的全部实现 javax.swing.Action 的全部实现 识别方法:命令模式能够经过抽象或接口类型(发送者)中的行为方法来识别,该类型调用另外一个不一样的抽象或接口类型(接收者)实现中的方法,该实现则是在建立时由命令模式的实现封装。