將請求轉換爲一個包含與請求相關的全部信息的獨立對象。該轉換讓你能根據不一樣的請求將方法參數化、延遲請求執行或將其放入隊列中,且能實現可撤銷操做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 的全部實現 識別方法:命令模式能夠經過抽象或接口類型(發送者)中的行爲方法來識別,該類型調用另外一個不一樣的抽象或接口類型(接收者)實現中的方法,該實現則是在建立時由命令模式的實現封裝。