设计模式之—命令模式
命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式 。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。它可将请求转换为一个包含与请求相关的所有信息的独立对象 。 该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中, 且能实现可撤销操作。
一句话核心:将一个 请求封装成一个对象 ,从而使可以用不同的请求对客户进行参数化。
主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行 记录、撤销、事务 等处理时,这种无法抵御变化的紧耦合的设计就不太合适。使用命令对象去分层。
何时使用: 在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。
如何解决: 通过调用者调用接受者执行命令,顺序:调用者→接受者→命令。
关键代码: 定义三个角色:1、received 真正的命令执行对象 2、Command 3、invoker 使用命令对象的入口
应用实例: SpringCloud的HystrixCommand使用了命令模式。
优点: 1、降低了系统耦合度。 2、新的命令可以很容易添加到系统中去。
缺点: 使用命令模式可能会导致某些系统有过多的具体命令类。
使用场景: 认为是命令的地方都可以使用命令模式,比如:
类似回调的地方,可以使用观察者模式实现。也可以考虑用命令模式实现一个请求-响应
,如GUI 中每一个按钮都是一条命令。
考虑到事务、撤销等场景
角色:
客户端 (Client) 会创建并配置具体命令对象。 客户端必须将包括接收者实体在内的所有请求参数传递给命令的构造函数。 此后, 生成的命令就可以与一个或多个发送者相关联了。
发送者 (Sender/Invoker)——负责对请求进行初始化, 其中必须包含一个成员变量来存储对于命令对象的引用。 发送者触发命令, 而不向接收者直接发送请求。 注意, 发送者并不负责创建命令对象: 它通常会通过构造函数从客户端处获得预先生成的命令。
命令接口 (Command) :通常仅声明一个执行命令的方法。
具体命令实现 (Concrete Commands) 会实现各种类型的请求。 具体命令自身并不完成工作, 而是会将调用委派给一个业务逻辑对象。 但为了简化代码, 这些类可以进行合并。
接收对象执行方法所需的参数可以声明为具体命令的成员变量。 你可以将命令对象设为不可变, 仅允许通过构造函数对这些成员变量进行初始化。
接收者 (Receiver) 类包含部分业务逻辑。 几乎任何对象都可以作为接收者。 绝大部分命令只处理如何将请求传递到接收者的细节, 接收者自己会完成实际的工作。
文本编辑器的举例
GUI上的Button持有很多Command类型,事件发生的时候生成并丢给Editor业务逻辑类去执行。并可以撤销。
客户端代码 (GUI 元素和命令历史等) 没有和具体命令类相耦合, 因为它通过命令接口来使用命令。 这使得你能在无需修改已有代码的情况下在程序中增加新的命令。
在代码中看上去就像这样: 一个 GUI 对象传递一些参数来调用一个业务逻辑对象。 这个过程通常被描述为一个对象发送请求 给另一个对象。(×,耦合严重)
【改进】:命令模式建议 GUI 对象不直接提交这些请求。 你应该将请求的所有细节 (例如调用的对象、 方法名称和参数列表) 抽取出来组成Command类 , 该类中仅包含一个用于触发请求的方法 。
命令对象负责连接不同的 GUI 和业务逻辑对象。 此后, GUI 对象无需了解业务逻辑对象是否获得了请求 , 也无需了解其对请求进行处理的方式。 GUI 对象触发命令即可, 命令对象会自行处理所有细节工作。
让我们回到文本编辑器。 应用命令模式后, 我们不再需要任何按钮子类来实现点击行为。 我们只需在 按钮
Button基类中添加一个成员变量来存储对于命令对象 的引用, 并在点击后执行该命令即可。你需要为每个可能的操作实现一系列命令类, 并且根据按钮所需行为将命令和按钮连接起来。
其他菜单、 快捷方式或整个对话框等 GUI 元素都可以通过相同方式来实现。 当用户与 GUI 元素交互时, 与其连接的命令将会被执行。 现在你很可能已经猜到了, 与相同操作相关的元素将会被连接到相同的命令, 从而避免了重复代码。
最后,命令成为了减少 GUI 和业务逻辑层之间耦合的中间层 而这仅仅是命令模式所提供的一小部分好处!
实例
一般command
中会有一个run/execute/do
类似的执行方法,还会有一个undo/fallBack
类似的反向撤销方法。
比如下面一个Command接口,里面有do
和undo
方法,同时他好几个不同的Command实现。
案例实现
优点:
命令抽象接口,定义一个无参方法,使得更通用
1 2 3 4 5 6 7 public interface Command { public int run () ; public int undo () ; }
命令实现----加法命令,一般只携带命令数据(为了简化也可以直接在命令里完成业务逻辑)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Data @AllArgsConstructor public class PlusCommand implements Command { private Calculator receiver; private int num1,num2; @Override public int run () { System.out.printf("执行加法run命令%d+%d=%d\n" ,num1,num2,num1+num2); return receiver.doPlus(num1,num2); } @Override public int undo () { System.out.printf("执行加法undo命令后的结果:%d\n" ,num1); return num1; } }
命令实现----乘法命令,一般只携带数据(为了简化也可以直接在命令里完成业务逻辑)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Data @AllArgsConstructor public class MultCommand implements Command { private Calculator receiver; private int num1,num2; @Override public int run () { return receiver.doMultiple(num1,num2); } @Override public int undo () { System.out.printf("执行惩乘法undo命令后的结果:%d\n" ,num1); return num1; } }
真正的业务逻辑处理方法(处理Command的地方)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Calculator { public int doPlus (int a, int b) { System.out.printf("receiver执行加法命令%d×%d=%d\n" ,a,b,a+b); return a + b; } public int doMultiple (int a, int b) { System.out.printf("receiver执行乘法命令%d×%d=%d\n" ,a,b,a*b); return a * b; } }
客户端和Invoker
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public class Main { public static void main (String[] args) { Client client = new Client (); client.business1Plus(10 ,2 ); client.business2Multiple(3 ,2 ); } } class Client { Calculator receiver = new Calculator (); public void business1Plus (int a, int b) { Command add = new PlusCommand (receiver, a, b); Invoker invoker = new Invoker (); invoker.setCommand(add); int res = invoker.executeCommand(); System.out.println("命令执行的结果是:" + res); } public void business2Multiple (int a, int b) { Command multiple = new MultCommand (receiver,a,b); Invoker invoker = new Invoker (); invoker.setCommand(multiple); int res = invoker.executeCommand(); System.out.println("命令执行的结果是:" + res); } } @Data class Invoker { private Command command; public int executeCommand () { return command.run(); } }
编辑器案例的伪代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 abstract class Command is protected field app: Application protected field editor: Editor protected field backup: text constructor Command (app: Application, editor: Editor) is this .app = app this .editor = editor method saveBackup () is backup = editor.text method undo () is editor.text = backup abstract method execute () class CopyCommand extends Command is method execute () is app.clipboard = editor.getSelection() return false class CutCommand extends Command is method execute () is saveBackup () app.clipboard = editor.getSelection() editor.deleteSelection() return true class PasteCommand extends Command is method execute () is saveBackup () editor.replaceSelection(app.clipboard) return true class UndoCommand extends Command is method execute () is app.undo() return false class CommandHistory is private field history: array of Command method push (c: Command) is method pop () :Command is class Editor is field text: string method getSelection () is method deleteSelection () is method replaceSelection (text) is class Application is field clipboard: string field editors: array of Editors field activeEditor: Editor field history: CommandHistory method createUI () is copy = function() { executeCommand( new CopyCommand (this , activeEditor)) } copyButton.setCommand(copy) shortcuts.onKeyPress("Ctrl+C" , copy) cut = function() { executeCommand( new CutCommand (this , activeEditor)) } cutButton.setCommand(cut) shortcuts.onKeyPress("Ctrl+X" , cut) paste = function() { executeCommand( new PasteCommand (this , activeEditor)) } pasteButton.setCommand(paste) shortcuts.onKeyPress("Ctrl+V" , paste) undo = function() { executeCommand( new UndoCommand (this , activeEditor)) } undoButton.setCommand(undo) shortcuts.onKeyPress("Ctrl+Z" , undo) method executeCommand (command) is if (command.execute) history.push(command) method undo () is command = history.pop() if (command != null ) command.undo()
https://refactoringguru.cn/design-patterns/command