C#面向对象设计模式纵横谈 学习笔记15 Command命令模式(行为型模式)

命令(Command)模式属于对象的行为模式【GOF95】。命令模式又称为行动(Action)模式或交易(Transaction)模式。命令模式把一个请求或者操作封装到一个对象中。命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。

每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。

命令模式和委托有很相似的一面,我们可以通过委托来理解Command模式

  1.  public delegate void ShowDelegate(string text);
  2.     
  3.     public class User
  4.     {
  5.         public event ShowDelegate UserDisplay;
  6.         
  7.         private string UserName;
  8.         
  9.         public User()
  10.         {
  11.         }
  12.         
  13.         public User(string UserName)
  14.         {
  15.             this.UserName = UserName;
  16.         }
  17.         
  18.         public void ShowUser()
  19.         {
  20.             if(UserDisplay != null)
  21.             {
  22.                 UserDisplay(UserName);
  23.             }
  24.         }
  25.         
  26.         public void ShowUserName(string UserName)
  27.         {
  28.             Console.WriteLine(UserName);
  29.         }
  30.     }
  31.     
  32.     public class Dept
  33.     {
  34.         private string DeptName;
  35.         public event ShowDelegate DeptDisplay;
  36.         
  37.         public Dept()
  38.         {
  39.         
  40.         }
  41.         
  42.         public Dept(string DeptName)
  43.         {
  44.             this.DeptName = DeptName;
  45.         }
  46.         
  47.         public void ShowDept()
  48.         {
  49.             if(DeptDisplay != null)
  50.             {
  51.                 DeptDisplay(DeptName);
  52.             }
  53.         }
  54.         
  55.         public void ShowDeptName(string DeptName)
  56.         {
  57.             Console.WriteLine(DeptName);
  58.         }
  59.     }

在上面这段代码中声明了两个类一个委托,我们先把事件和事件相关的函数过滤掉

  1.  static void Main(string[] args)
  2.         {
  3.             User objUser = new User();
  4.             Dept objDept = new Dept();
  5.             
  6.             ShowDelegate userDelegate = new ShowDelegate(objUser.ShowUserName);
  7.             ShowDelegate deptDelegate = new ShowDelegate(objDept.ShowDeptName);
  8.             
  9.             userDelegate("username");
  10.             deptDelegate("DeptName");
  11.             
  12.             Console.Read();
  13.             
  14.         }

在上面这段代码中,我们利用委托调用了ShowUserName方法和ShowDeptName方法。大家很奇怪,为什么这样调用,这样不是把一个简单的问题复杂话了。当然在这里我们看不出来委托的效果,但是结合事件,我们就可以很快的看到委托的作用

  1. static void Main(string[] args)
  2.         {
  3.             User objUser = new User("zhangsan");
  4.             objUser.UserDisplay += new ShowDelegate(objUser_UserDisplay);
  5.             
  6.             objUser.ShowUser();
  7.             
  8.             Console.Read();
  9.         }
  10.         
  11.        
  12.         static void objUser_UserDisplay(string text)
  13.         {
  14.             Console.WriteLine(text);
  15.         }

在上面这段代码中,我们利用的事件,事件实际上就是规定了函数签名的消息,他规定了处理此事件的函数签名(就是委托),当触发到这个事件时,就向外部发送消息。那么我们可以看到objUser_UserDisplay这个方法是符合ShowDelegate委托规定的函数签名的。那么当触发事件之后,  这句代码就会UserDisplay(UserName)间接的调用ojbUser_UserDisplay的。

可以看到利用委托调用方法实际上就是一种Command模式

在上面这段代码中,我们利用的事件,事件实际上就是规定了函数签名的消息,他规定了处理此事件的函数签名(就是委托),当触发到这个事件时,就向外部发送消息。那么我们可以看到objUser_UserDisplay这个方法是符合ShowDelegate委托规定的函数签名的。那么当触发事件之后,  这句代码就会UserDisplay(UserName)间接的调用ojbUser_UserDisplay的。

可以看到利用委托调用方法实际上就是一种Command模式

概述

在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,可以实现二者之间的松耦合[李建忠]。这就是本文要说的Command模式。

意图

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。[GOF 《设计模式》]

这里借用一下别人写的一个例子

  1.   public abstract class DocumentCommand
  2.     {
  3.         Document _document;
  4.         public DocumentCommand(Document doc)
  5.         {
  6.             this._document = doc;
  7.         }
  8.         /**//// <summary>
  9.         /// 执行 
  10.         /// </summary> 
  11.         public abstract void Execute();
  12.     }
  13.     public class DisplayCommand : DocumentCommand
  14.     {
  15.         public DisplayCommand(Document doc)
  16.             : base(doc)
  17.         {    
  18.         }
  19.         public override void Execute()
  20.         {
  21.             _document.Display();   
  22.         }
  23.     }
  24.     
  25.     public class UndoCommand : DocumentCommand
  26.     { 
  27.         public UndoCommand(Document doc)
  28.             : base(doc)
  29.         {   
  30.         }
  31.         public override void Execute()
  32.         {
  33.             _document.Undo();   
  34.         }
  35.     }
  36.     
  37.     public class RedoCommand : DocumentCommand
  38.     {
  39.         public RedoCommand(Document doc)
  40.             : base(doc)
  41.         { 
  42.         }
  43.         public override void Execute()
  44.         {
  45.             _document.Redo();   
  46.         } 
  47.     }
  48.     
  49.     public class DocumentInvoker
  50.     {
  51.         DocumentCommand _discmd;
  52.         DocumentCommand _undcmd;
  53.         DocumentCommand _redcmd;
  54.         public DocumentInvoker(DocumentCommand discmd,DocumentCommand undcmd,DocumentCommand redcmd)
  55.         {
  56.             this._discmd = discmd;
  57.             this._undcmd = undcmd;
  58.             this._redcmd = redcmd;
  59.         }
  60.         public void Display()
  61.         {
  62.             _discmd.Execute();
  63.         }
  64.         public void Undo()
  65.         {
  66.             _undcmd.Execute();
  67.         }
  68.         public void Redo()
  69.         {
  70.             _redcmd.Execute();
  71.         }
  72.     }
  73.     
  74.     class Program
  75.     {
  76.         static void Main(string[] args)
  77.         {
  78.             Document doc = new Document();
  79.             DocumentCommand discmd = new DisplayCommand(doc);
  80.             DocumentCommand undcmd = new UndoCommand(doc);
  81.             DocumentCommand redcmd = new RedoCommand(doc);
  82.             DocumentInvoker invoker = new DocumentInvoker(discmd,undcmd,redcmd);
  83.             invoker.Display();
  84.             invoker.Undo();
  85.             invoker.Redo();
  86.         }
  87.     }

可以看到上面的DocumentCommand类,将他替换成委托,doc替换成函数签名,invoker的调用替换成为委托的调用,整个的调用过程与委托没有什么区别,在这段代码中,我们将display、undo、redo三个命令抽象成了三个类,通过Invoker调用这三个命令,这三个命令再来调用对应的document中的方法,达到解藕的目的。

那么委托可以实现事件处理器的动态调用,而通过使用Command设计模式能否实现事件处理器的动态调用。答案当然是可以,在Java里面就是这样实现的,这里用c#简单的模拟一下java的事件机制


 

  1. class MyEventArgs
  2.     {
  3.         public string message;
  4.         public MyEventArgs(string message)
  5.         {
  6.             this.message = message;
  7.         }
  8.     }
  9.     interface IClickListener
  10.     {
  11.         void Click(MyEventArgs e);
  12.     }
  13.     class ClickListener : IClickListener
  14.     {
  15.         #region IClickListener 成员
  16.         public void Click(MyEventArgs e)
  17.         {
  18.             Console.WriteLine("Click Event occured. message is " + e.message);
  19.         }
  20.         #endregion
  21.     }
  22.     class Button
  23.     {
  24.         private ArrayList listenerList = new ArrayList();
  25.         
  26.         public void AddListener(IClickListener listener)
  27.         {
  28.             lock(this)
  29.             {
  30.                 listenerList.Add(listener);
  31.             }
  32.         }
  33.         
  34.         public void RemoveListener(IClickListener listener)
  35.         {
  36.             lock(this)
  37.             {
  38.                 listenerList.Remove(listener);
  39.             }
  40.         }
  41.         
  42.         public void DoClickEvent()
  43.         {
  44.             lock(this)
  45.             {
  46.                 MyEventArgs args = new MyEventArgs("zhangsan");
  47.                 
  48.                 IEnumerator em = listenerList.GetEnumerator();
  49.                 
  50.                 while(em.MoveNext())
  51.                 {
  52.                     ((IClickListener)em.Current).Click(args);
  53.                 }
  54.             }
  55.         }
  56.         
  57.         public void ButtonClick()
  58.         {
  59.             //do something
  60.             DoClickEvent();
  61.         }
  62.     }
  63.     
  64.     public class Program
  65.     {
  66.         static void Main()
  67.         {
  68.             Button button = new Button();
  69.             ClickListener clickListener = new ClickListener();
  70.             button.AddListener(clickListener);
  71.             
  72.             button.ButtonClick();
  73.             
  74.             Console.Read();
  75.         }
  76.     }

从上面的代码我们可以看到IClickListener接口实际定义了需要执行命令的接口,ClickListener类实现了命令接口,实际上他就是一个命令对象。我们定义了一个Button对象,通过Button对象的AddListener方法注册了一个处理的命令,并且保存在一个ArrayList中,当我们Button被Click的时候,我们从ArrayList里面取出注册的命令类,并且执行这个命令类。ClickListener 的Click方法,相当于我们的事件处理器。而IClickListener接口相当于委托,他规定了函数的签名,Button类的ButtonClick方法发送消息,让Command列表进行执行。

效果及实现要点

1Command模式的根本目的在于将“行为请求者”与“行为实现者”解耦,在面向对象语言中,常见的实现手段是“将行为抽象为对象”。

2.实现Command接口的具体命令对象ConcreteCommand有时候根据需要可能会保存一些额外的状态信息。

3.通过使用Compmosite模式,可以将多个命令封装为一个“复合命令”MacroCommand

4Command模式与C#中的Delegate有些类似。但两者定义行为接口的规范有所区别:Command以面向对象中的“接口-实现”来定义行为接口规范,更严格,更符合抽象原则;Delegate以函数签名来定义行为接口规范,更灵活,但抽象能力比较弱。Delegate的效率较低一些。

5.使用命令模式会导致某些系统有过多的具体命令类。某些系统可能需要几十个,几百个甚至几千个具体命令类,这会使命令模式在这样的系统里变得不实际。

适用性

在下面的情况下应当考虑使用命令模式:

1.使用命令模式作为"CallBack"在面向对象系统中的替代。"CallBack"讲的便是先将一个函数登记上,然后在以后调用此函数。

2.需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。

3.系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。

4.如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。

总结

Command模式是非常简单而又优雅的一种设计模式,它的根本目的在于将“行为请求者”与“行为实现者”解耦。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值