zoukankan      html  css  js  c++  java
  • 设计模式(16) 命令模式

    • 命令模式
    • 适用场景
    • Redo & Undo
    • 命令模式的优缺点

    命令模式

    命令模式是对一类对象公共操作的抽象,它们具有相同的方法签名,所以具有类似操作,可以被抽象出来,成为一个抽象的“命令”对象。请求以命令的形式包裹在对象中,并传给调用对象。调用者寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。这样实际操作的调用者就不是和一组对象打交道,它只需要依赖于这个“命令”对象的方法签名,并根据这个操作签名调用相关的方法。

    GOF对命令模式描述为:
    Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests,and support undoable operations...
    — Design Patterns : Elements of Reusable Object-Oriented Software

    UML类图:

    代码示例:

    public interface ICommand
    {
        void Execute();
        Receiver Receiver { set; }
    }
    
    public class Receiver
    {
        public string Name { get; private set; }
        public string Address { get; private set; }
    
        public void SetName()
        {
            this.Name = "Name";
        }
    
        public void SetAddress()
        {
            this.Name = "Address";
        }
    }
    
    public abstract class CommandBase : ICommand
    {
        public Receiver Receiver { set; get; }
    
        public abstract void Execute();
    }
    
    public class SetAddressCommand : CommandBase
    {
        public override void Execute()
        {
            base.Receiver.SetName();
        }
    }
    
    public class SetNameCommand : CommandBase
    {
        public override void Execute()
        {
            base.Receiver.SetAddress();
        }
    }
    
    public class Invoker
    {
        private IList<ICommand> commands = new List<ICommand>();
        public void AddCommand(ICommand command)
        {
            commands.Add(command);
        }
    
        public void Run()
        {
            foreach (ICommand command in commands)
            {
                command.Execute();
            }
        }
    }
    

    Client代码:

    Receiver receiver = new Receiver();
    ICommand command1 = new SetNameCommand();
    ICommand command2 = new SetAddressCommand();
    command1.Receiver = receiver;
    command2.Receiver = receiver;
    Invoker invoker = new Invoker();
    invoker.AddCommand(command1);
    invoker.AddCommand(command2);
    invoker.Run();
    

    适用场景

    • 调用者同时与多个执行对象交互,而且每个操作可以抽象为近似的形式。
    • 我们需要控制调用本身的生命期,而不是调用者直截了当地进行一个调用,有可能根据需要合并、分配、疏导相关的调用。
    • 一系列类似的调用可能需要辅以Redo()或Undo()之类的特性。
    • 类似以往函数指针,需要在执行一个调用的同时告诉它需要回调那些操作。
    • 方法本身太过复杂,从整个项目重用的角度考虑,需要把方法的实现抽象为一组可以协作的对象。

    Redo & Undo

    再来看看如何用命令模式实现Redo和Undo,要实现Redo和Undo就需要保存执行过的命令,并通过安排这些命令的执行顺序来达到目地。
    以SQL的执行为例,下面的代码定义了SQLExecute作为Receiver,CommandManager作为Invoker,InsertIntoCommand作为ConcreteCommand:

    public interface ICommand
    {
        public void Execute();
        public void Undo();
    }
    
    public class SQLExcute
    {
        public void InsertInto(string id)
        {
            Console.WriteLine("插入一条数据,id:" + id);
        }
    
        public void Delete(string id)
        {
            Console.WriteLine("删除一条数据,id:" + id);
        }
    }
    
    public class InsertIntoCommand : ICommand
    {
        private SQLExcute sqlExcute;
        private string id;
        public InsertIntoCommand(SQLExcute sqlExcute, string id)
        {
            this.sqlExcute = sqlExcute;
            this.id = id;
        }
    
        public void Execute()
        {
            sqlExcute.InsertInto(id);
        }
    
        public void Undo()
        {
            sqlExcute.Delete(id);
        }
    }
    
    public class CommandManager
    {
        private Stack<ICommand> undoStacks = new Stack<ICommand>();
        private Stack<ICommand> redoStacks = new Stack<ICommand>();
    
        public void Execute(ICommand command)
        {
            command.Execute();
            undoStacks.Push(command);
            if (redoStacks.Count > 0)
            {
                redoStacks.Clear();
            }
        }
    
        public void Undo()
        {
            if (undoStacks.Count > 0)
            {
                ICommand pop = undoStacks.Pop();
                pop.Undo();
                redoStacks.Push(pop);
            }
        }
    
        public void Redo()
        {
            if (redoStacks.Count > 0)
            {
                ICommand pop = redoStacks.Pop();
                pop.Execute();
            }
        }
    }
    

    Client代码:

    CommandManager manager = new CommandManager();
    SQLExcute excute = new SQLExcute();
    InsertIntoCommand command1 = new InsertIntoCommand(excute, "1");
    InsertIntoCommand command2 = new InsertIntoCommand(excute, "2");
    manager.Execute(command1);
    manager.Execute(command2);
    
    Console.WriteLine("undo------------");
    manager.Undo(); 
    manager.Undo();
    Console.WriteLine("redo------------");
    manager.Redo();
    manager.Redo();
    

    运行结果:

    插入一条数据,id:1
    插入一条数据,id:2
    undo------------
    删除一条数据,id:2
    删除一条数据,id:1
    redo------------
    插入一条数据,id:1
    插入一条数据,id:2
    

    命令模式的优缺点

    可以看到使用命令模式,调用者并不需要直接与实际的执行者打交道,实现了两者的解耦,此外基于命令的机制,可以方便地做一些类似Undo, Redo的扩展,具体的优点有:
    优点:

    • 命令模式将请求一个操作的对象与具体执行一个操作的对象分割开,符合开闭原则和迪米特法则
    • 能较容易的设计一个命令队列
    • 在需要的情况下,可以容易的将命令计入日志
    • 允许接收请求的一方决定是否接受请求
    • 可以容易的实现对请求的Undo,Redo
    • 由于加进新的具体命令类不影响其他的类,因此便于扩展

    缺点:
    命令模式也有其固有的缺点:在命令扩充至较多的数量时,便需要创建对应数量的ConcreteCommand,命令类过多,系统的维护会比较复杂。

    参考书籍:
    王翔著 《设计模式——基于C#的工程化实现及扩展》

  • 相关阅读:
    用户组
    Compose
    ubuntu下不同版本python安装pip及pip的使用
    rest-framework-@action()装饰器
    数据库数据导出CSV文件,浏览器下载
    爬取拉钩网信息
    CSV模块
    DOM对象之查找标签&属性操作
    Java内存模型 一
    SQL优化之一
  • 原文地址:https://www.cnblogs.com/zhixin9001/p/13443075.html
Copyright © 2011-2022 走看看