Design Pattern - 命令模式
一般执行一个操作的过程, 创建对象, 并调用对象的函数, 函数执行, 返回
比如下面的类图, client直接调用Receiver.action
而命令模式, 抽象出command对象, 并在command对象封装对Receiver.action的调用
而client只负责创建command对象(invocation), 并提交给Invoker(通过setCommand), 而command真正的执行(execution)由Invoker控制
从而实现invocation和invoker的分离和解耦合
当然会问, 直接调用那么简单, 为什么要绕那么大的圈来用command?
a. 简化调用过程, 整个调用过程比较繁杂或调用前后需要进行某些额外处理,比如日志,缓存,记录历史操作等
b. 作为"CallBack"在面向对象系统中的替代
c. 支持undo, redo
d. 更关键的是, 命令模式往往会用于异步或并发处理模式
比如, producer和consumer模式, producer可以不断产生command放到queue里面, 然后可以通过consumer来异步的执行, 达到并发和异步
命令模式 (Command), 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
这个模式的基本思路是解耦合和责任单一, “行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的, 所以要使用命令模式来解耦合.
场景, 这个模式在平时用的很多, 编辑器里面很常用的'undo/redo', 就是用的这种模式.
经常举的例子就是点菜, 客人可以直接对厨师进行点菜, 这样做有两点不好
1. 客户和厨师紧耦合, 比如厨师换了, 或菜名换了等等, 客户代码需要跟着改变.
2. 厨师除了要烹饪外, 还需要记录每个客户请求, 当客户比较多时, 且客户请求是会变化的, 换个菜, 退个菜, 这个厨师就忙不过来了, 很容易记错. 这就违反了单一责任原则, 厨师就应该负责烹饪, 其他的事应该有专门的waiter负责.
这就是命令模式, 当你需要对请求者的请求(函数调用)进行记录, undo, redo等操作时, 需要单独抽象出Invoker类来处理, 而不要直接耦合在Receiver中.
上面的例子中, 客户是Client, 服务生是Invoker, 厨师是Receiver, 客户请求是Command
class Receiver //最终命令的执行者, 厨师 { public void Action() { //具体的命令执行, 烹饪菜肴 } } class ConcreteCommand : Command //具体的客户请求, 点一道菜 { protected Receiver rec; //该情况的执行者, 这道菜哪个厨师烧 public Command(Receiver rec) { this.rec = rec; } public void Execute() { rec.Action()//让执行者执行命令 } } class Invoker //Command模式的核心, 客户请求的管理者, 服务生waiter { private List<Command> cmds = new List<Command>(); //用于保存客户请求 public void SetOrder(Command cmd) { if Valid(cmd) //需要判断请求是否合理, 比如是否有这道菜 { cmds.Add(cmd); //增加请求 } } public void CancelOrder(Command cmd) { cmds.Remove(cmd); //删除请求 } public void Execute() //执行所有请求 { for cmd in cmds { cmd.Excute(); } } }
public class Client { public void client(){ //创建接收者 Receiver receiver = new Receiver(); //创建命令对象,设定它的接收者 Command command = new ConcreteCommand(receiver); //创建Invoker,把命令对象设置进去 Invoker invoker = new Invoker(); invoker.SetOrder(command); } }
Invoker具体的实现可能变化多端, 但是本质就是通过Invoke来把客户请求抽象成command, 并且保存和管理commands, 向用户提供如do, redo, undo, rollback等操作, 而这些操作对receiver都是透明的, 这取决于客户和receiver的解耦.