设计模式学习之命令模式(Command,行为型模式)(12)

一.命令模式的定义

命令模式属于对象的行为型模式。命令模式是把一个操作或者行为抽象为一个对象中,通过对命令的抽象化来使得发出命令的责任和执行命令的责任分隔开。命令模式的实现可以提供命令的撤销和恢复功能。

二.我们来看一个类图

从命令模式的结构图可以看出,它涉及到五个角色,它们分别是:

  • 客户角色:发出一个具体的命令并确定其接受者。
  • 命令角色:声明了一个给所有具体命令类实现的抽象接口
  • 具体命令角色:定义了一个接受者和行为的弱耦合,负责调用接受者的相应方法。
  • 请求者角色:负责调用命令对象执行命令。
  • 接受者角色:负责具体行为的执行。

三、生活中的例子  

1.场景模拟

        首先我们考虑,当项目多,人手不够时,我们需要一个人去同时兼有好几个项目的工作,可能有开发也可能有运维!

        那么第一步我们先设计一个项目类,分别代表需要参与的项目,代码如下:

复制代码
    /// <summary>
    /// Web项目类
    /// </summary>
    public class WebProject
    {
        /// <summary>
        /// 项目名称
        /// </summary>
        public string ProjectName { get; set; }
    }
复制代码

         接下来第二步,我们设计一个开发者类表示具体的开发人员,并且开发人员具有开发项目和运维项目的操作,代码如下:

复制代码
    /// <summary>
    /// 开发者
    /// </summary>
    public class Developer
    {
        /// <summary>
        /// 开发项目
        /// </summary>
        /// <param name="project">项目</param>
        public void Coding(WebProject project)
        {
            Console.WriteLine("开发{0}项目",project.ProjectName);
        }

        /// <summary>
        /// 运维项目
        /// </summary>
        /// <param name="project">项目</param>
        public void UpdateRoutine(WebProject project)
        {
            Console.WriteLine("维护{0}项目", project.ProjectName);
        }
    }
复制代码

        主函数调用方法如下:

复制代码
    static void Main(string[] args)
    {
            //表示一个具体的项目名字叫:项目A
            WebProject wpA = new WebProject() { ProjectName = "项目A" };
            //表示一个具体的项目名字叫:项目B
            WebProject wpB = new WebProject() { ProjectName = "项目B" };

            //实例化一个开发者,程序员
            Developer dp = new Developer();

            //命令他开发项目A
            dp.Coding(wpA);
            //命令他开发项目B
            dp.UpdateRoutine(wpB);
    }
复制代码

        这么简单的程序运行结果我就不贴出来啦,我们来看看上边的代码,我们把主函数的调用当做客户,我们发现如果客户直接和开发者形成这种紧耦合的状态是非常不好的,比如说,客户会提出很多业务上的要求,而开发者并不能很准确的估算出开发成本与时间,所以我们需要一个项目经理来与客户沟通,并且将客户提出的不合理需求过滤掉,在给开发人员发出一个命令将项目交给开发人员!也就是说需要将“命令发起者”与“命令实现者”分离!

        2.命令模式

         那么我们来看看实现“命令发起者”与“命令实现者”分离的设计模式,命令模式:

         命令模式:将一个请求封装为一个对象,从而可用不同的的请求对客户进行参数化,队请求排队或者记录请求日志,以及支持可撤销的操作。

         命令模式结构图如下:

      

       ①接口ICommand:用来声明抽象一个命令操作的接口。

       ②ConcreteCommand:具体的命令实现,用来表示命令的类型例如上边场景中的开发项目和维护项目可分别实现为:开发项目命令,维护项目命令等。

       ③Receiver:具体命令的实现者,也就是说命令的执行者,相对于上边场景中的开发者。

       ④Invoker:命令的指挥者,用来设置命令,并且通知执行命令,相对于上边场景中的项目经理。

       了解完命令模式的概念后,我们用命令模式来实现上边的场景:

        首先是命令接口ICommand的实现,代码如下:

    public interface ICommand
    {
        void Excute(WebProject project);
    }

        接下来就是我们接受命令并且执行的实现者啦,也就是我们场景中的开发者,代码如下:

复制代码
    /// <summary>
    /// 开发者
    /// </summary>
    public class Developer
    {
        /// <summary>
        /// 开发项目
        /// </summary>
        /// <param name="project">项目</param>
        public void Coding(WebProject project)
        {
            Console.WriteLine("开发{0}项目", project.ProjectName);
        }

        /// <summary>
        /// 运维项目
        /// </summary>
        /// <param name="project">项目</param>
        public void UpdateRoutine(WebProject project)
        {
            Console.WriteLine("维护{0}项目", project.ProjectName);
        }
    }
复制代码

       现在有了命令的实现者,就应该有具体的命令啦,上边那场景中的命令分别有,开发命令与维护命令,具体代码如下:

复制代码
  /// <summary>
    /// 开发项目命令
    /// </summary>
    public class CodingCommand : ICommand
    {
        Developer dp { get; set; }
        public WebProject project { get; set; }//所对应的项目

        public void Excute()
        {
            dp.Coding(project);
        }
    }

    /// <summary>
    /// 维护项目命令
    /// </summary>
    public class UpdateRoutineCommand : ICommand
    {
        Developer dp { get; set; }
        public WebProject project { get; set; }//所对应的项目

        public void Excute()
        {
            dp.UpdateRoutine(project);
        }
    }
复制代码

       命令与实现者都就绪了,指挥调度者就该出现了,也就是项目经理,项目经理用来负责设置命令调度命令执行,代码如下:

复制代码
    /// <summary>
    /// 项目经理类
    /// </summary>
    public class ProjectManager
    {
        List<ICommand> cmdList = new List<ICommand>();

        /// <summary>
        /// 设置命令
        /// </summary>
        /// <param name="cmd"></param>
        public void SetCommand(ICommand cmd)
        {
            cmdList.Add(cmd);
        }

        /// <summary>
        /// 发起命令
        /// </summary>
        public void ExcuteCommand()
        {
            cmdList.ForEach(p => p.Excute());
        }
    }
复制代码

       代码写好后,我们来模拟一下调用吧,现在项目经理需要分配给开发者两个任务,一个是开发项目A,另一个是维护项目B!主函数调用如下:

复制代码
            //表示一个具体的项目名字叫:项目A
            WebProject wpA = new WebProject() { ProjectName = "项目A" };
            //表示一个具体的项目名字叫:项目B
            WebProject wpB = new WebProject() { ProjectName = "项目B" };
            //开发者
            Developer developer= new Developer();

            //开发者所需要接收的命令
            ICommand codeCmd = new CodingCommand() { project = wpA, dp = developer };
            ICommand UpdateCmd = new UpdateRoutineCommand() { project = wpB, dp = developer };

            //项目经理
            ProjectManager pm = new ProjectManager();
            //设置命令
            pm.SetCommand(codeCmd);
            pm.SetCommand(UpdateCmd);
            //发起命令让开发者去完成
            pm.ExcuteCommand();
}
复制代码

       那么一个简单的命令模式就完成了。

      3.更加灵活的分配任务

       接下来我们在增加一些要求:

       ①项目经理每次发起的命令都需要记录下来,年底好根据开发者的工作量评估奖金。

       ②项目经理可以撤销发起的命令,例如:撤销维护项目B的命令,让开发者专心做项目A的开发工作。

       回顾下命令模式的定义,上边两条需求就等同于定义中的“队请求排队或者记录请求日志,以及支持可撤销的操作

       那么我们给需求中加入一个方法用来撤销命令,因为项目经理类中已经有一个集合来记录命令的总数了,所以已经实现了请求记录的功能啦,我们只需加入撤销功能就好了。

       首先,给ICommand接口加入一个Cancel方法,用来表示撤销操作,因为命令撤销了需要通知实现者做一些撤销的工作,代码如下:

    public interface ICommand
    {
        void Excute();
        void Cancel();
    }

      接下来既然有取消了,那么开发者受到取消命令后要执行一些关于取消的代码,所以开发者类加入一个方法StopCoding,表示停止项目的工作,代码如下:

复制代码
  /// <summary>
    /// 开发者
    /// </summary>
    public class Developer
    {
        /// <summary>
        /// 开发项目
        /// </summary>
        /// <param name="project">项目</param>
        public void Coding(WebProject project)
        {
            Console.WriteLine("开发{0}项目", project.ProjectName);
        }

        /// <summary>
        /// 运维项目
        /// </summary>
        /// <param name="project">项目</param>
        public void UpdateRoutine(WebProject project)
        {
            Console.WriteLine("维护{0}项目", project.ProjectName);
        }

        /// <summary>
        /// 停止项目的工作
        /// </summary>
        /// <param name="project"></param>
        public void StopCoding(WebProject project)
        {
            Console.WriteLine("停止{0}项目",project.ProjectName);
        }
    }
复制代码

      修改实际的命令类实现撤销命令的方法,并调用开发者类的StopCoding方法做撤销命令的工作,代码如下:

复制代码
/// <summary>
    /// 开发项目命令
    /// </summary>
    public class CodingCommand : ICommand
    {
        public Developer dp { get; set; }
        public WebProject project { get; set; }//所对应的项目

        public void Excute()
        {
            dp.Coding(project);
        }

        /// <summary>
        /// 撤销操作
        /// </summary>
        public void Cancel()
        {
            dp.StopCoding(project);
        }
    }

    /// <summary>
    /// 维护项目命令
    /// </summary>
    public class UpdateRoutineCommand : ICommand
    {
        public Developer dp { get; set; }
        public WebProject project { get; set; }//所对应的项目

        public void Excute()
        {
            dp.UpdateRoutine(project);
        }

        /// <summary>
        /// 撤销操作
        /// </summary>
        public void Cancel()
        {
            dp.StopCoding(project);
        }
    }
复制代码

       接下来就是命令的指挥者啦,项目经理首先设置一个撤销命令,并且将执撤销命令,在执行撤销命令的时候首先要将命令从集合中移除,因为该开发者已经不参与该撤销项目的工作了,所以不记录考核依据中,在调用命令中的撤销方法让开发者做具体的撤销工作。代码如下:

复制代码
/// <summary>
    /// 项目经理类
    /// </summary>
    public class ProjectManager
    {
        List<ICommand> cmdList = new List<ICommand>();

        /// <summary>
        /// 设置命令
        /// </summary>
        /// <param name="cmd"></param>
        public void SetCommand(ICommand cmd)
        {
            cmdList.Add(cmd);
        }

        /// <summary>
        /// 发起命令
        /// </summary>
        public void ExcuteCommand()
        {
            cmdList.ForEach(p => p.Excute());
        }

        /// <summary>
        /// 返回记录行数
        /// </summary>
        /// <returns></returns>
        public int GetCommandCount()
        {
            return cmdList.Count;
        }

        /// <summary>
        /// 撤销命令
        /// </summary>
        /// <param name="cmd"></param>
        public void CancelExectueCommand(ICommand cmd)
        {
            cmd.Cancel();
            cmdList.Remove(cmd);
        }
    }
复制代码

     可见对功能的封装和“请求与实现分离”,可以让我们很容易的进行撤销与记录的工作,可以再命令执行前作一些必须要的操作。主函数调用如下:

复制代码
        static void Main(string[] args)
        {
            //表示一个具体的项目名字叫:项目A
            WebProject wpA = new WebProject() { ProjectName = "项目A" };
            //表示一个具体的项目名字叫:项目B
            WebProject wpB = new WebProject() { ProjectName = "项目B" };
            //开发者
            Developer developer= new Developer();

            //开发者所需要接收的命令
            ICommand codeCmd = new CodingCommand() { project = wpA, dp = developer };
            ICommand UpdateCmd = new UpdateRoutineCommand() { project = wpB, dp = developer };

            //项目经理
            ProjectManager pm = new ProjectManager();
            //设置命令
            pm.SetCommand(codeCmd);
            pm.SetCommand(UpdateCmd);
            //发起命令让开发者去完成
            pm.ExcuteCommand();

            pm.CancelExectueCommand(UpdateCmd);//撤销UpdateCmd命令
         }
复制代码

       命令模式总结:

       ①它能很容易的维护所有命令的集合。

       ②它可以很方便的实现撤销和恢复命令。

       ③可以很方便的将每个执行记录日志。

       ④最重要的就是将发起者与实现者分离。

参考链接:http://www.cnblogs.com/doubleliang/archive/2012/04/14/2447815.html

原文地址:https://www.cnblogs.com/yxlblogs/p/4000525.html