zl程序教程

您现在的位置是:首页 >  其他

当前栏目

Design Pattern - 命令模式

2023-03-14 22:35:20 时间

一般执行一个操作的过程, 创建对象, 并调用对象的函数, 函数执行, 返回

比如下面的类图, 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

image_thumb[1]

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的解耦. 


本文章摘自博客园,原文发布日期:2013-11-19