定义
将一个请求封装成一个对象,让你可以用不同的请求对客户端进行参数化,对请求排队,纪录请求日志和支持可撤消操作.
有三个具体成员,请求的发送者,请求的接收者,还有就是请求本身(或者是命令).对客户端进行参数化,也就是客户端可以把请求对象当成一个参数,直接注入到请求发送者内部,不用管请求接收者;对请求排队,如果一个请求发送者发送了一个请求,有多个接收者会处理这个请求的话,那么就需要对命令排队,命令对象和请求接收者是一一对应的;纪录请求日志,当发出一个请求时,由命令对象来保存一些信息,证明本对象被使用了,也可以说是本命令被处理了;支持可撤消操作,比如一个加法命令,命令对象纪录下加数,通过减去这个加数就实现了一次撤消,通过再加上这个加数就实现了一次恢复.
结构组成
从定义中,可以看出,一次完整的命令(请求)处理需要有三种类型成员参与.
抽象命令类(Command):声明了执行请求的方法Execute,方法中调用请求接收者的处理方法.
具体命令类(ConcreteCommand):实现方法Execute,每个具体命令类对应一个请求处理者.
调用者(Invoke):发送命令者,维护一个命令对象的引用.调用命令类的Execute方法.
接收者(Receiver):定义了用于处理请求的业务方法.最终的处理还是在业务方法中.
示例
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 using System.Configuration; 5 using System.Reflection; 6 7 namespace 命令模式 8 { 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 Configuration cfa = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); 14 string comm = cfa.AppSettings.Settings["commandfan"].Value;//这是得到风扇命令类 15 Assembly assembly = Assembly.GetEntryAssembly(); 16 Command command =(Command) assembly.CreateInstance(comm); 17 ButtonStart bs = new ButtonStart(command);//注入到调用者中 18 bs.Start();//调用者执行调用,直接调用的是命令类对象的方法,实质最终的响应还是接收者完成 19 Console.Read(); 20 } 21 } 22 class ButtonStart 23 { 24 private Command com = null; 25 public ButtonStart(Command com) 26 { 27 this.com = com; 28 } 29 public void Start() 30 { 31 com.Execute();//执行命令类的方法 32 } 33 } 34 abstract class Command 35 { 36 public abstract void Execute(); 37 } 38 class FanCommand : Command//风扇命令类 39 { 40 private Fan fan = null; 41 public FanCommand() 42 { 43 fan = new Fan(); 44 } 45 public override void Execute() 46 { 47 fan.StartUP(); 48 } 49 } 50 class BulbCommand : Command 51 { 52 private Bulb bulb= null; 53 public BulbCommand() 54 { 55 bulb = new Bulb();//调用命令接收者的业务方法 56 } 57 public override void Execute() 58 { 59 bulb.Light();//调用命令接收者的业务方法 60 } 61 } 62 class Fan//风扇类,命令接收者 63 { 64 public void StartUP() 65 { 66 Console.WriteLine("风扇启动"); 67 } 68 } 69 class Bulb//灯泡类,命令接收者 70 { 71 public void Light() 72 { 73 Console.WriteLine("发光"); 74 } 75 } 76 }
感觉实际上还是按一条链那样调用的样子。看不出太多的优势。
命令队列,纪录日志,撤消操作
命令队列适用的情况:当一个请求发出后,会有多个请求接收者响应,也就是有多个命令对象来“中间处理”;在命令发送者类中,维护一个命令列表,当请求发出时,依次调用命令队列中命令对象的方法。示例如下:
1 class ButtonStart 2 { 3 private Command com = null; 4 public List<Command> lstComm = new List<Command>();//维护命令队列 5 public void StartAll()//启动所有命令 6 { 7 foreach (Command com in lstComm) 8 { 9 com.Execute(); 10 } 11 } 12 public ButtonStart() 13 { 14 } 15 public ButtonStart(Command com) 16 { 17 this.com = com; 18 } 19 public void Start() 20 { 21 com.Execute();//执行命令类的方法 22 } 23 } 24 25 26 27 //客户端调用情况 28 //命令队列 29 ButtonStart bsque = new ButtonStart(); 30 bsque.lstComm.Add(new FanCommand()); 31 bsque.lstComm.Add(new BulbCommand()); 32 bsque.StartAll(); 33 34 /* 35 在调用时,发送者类中的一次调用,就让所有命令对象来响应了,最终反映到所有接收者的响应中。 36 这次调用,也体现到了多态的作用。 37 */
还可以把对多个命令的组织放到一个独立的对象中,对象对应的类结构大概如下:
1 class QueueCommand 2 { 3 private List<Command> lstComm = new List<Command>(); 4 public void Add(Command com)//添加成员 5 { 6 lstComm.Add(com); 7 } 8 public void Execute()//执行此方法,循环调用多个命令对象的方法 9 { 10 foreach (Command com in lstComm) 11 { 12 com.Execute(); 13 } 14 } 15 }
纪录日志:
当一个命令执行前,把这个操作纪录下来。方法是在某个命令对象执行Excute方法时,在调用对应的接收者的业务方法前,把一些有纪录意义的文本保存到“日志文件”中。
1 class BulbCommand : Command 2 { 3 public string blog = "发光日志"; 4 private Bulb bulb= null; 5 public BulbCommand() 6 { 7 bulb = new Bulb();//调用命令接收者的业务方法 8 } 9 public override void Execute() 10 { 11 Console.WriteLine("模拟灯泡被开启日志"+blog); 12 bulb.Light();//调用命令接收者的业务方法 13 } 14 }
结合序列化等,把对象序列化到硬盘上。
可撤消操作:
当一个命令对象执行Excute方法后,可以再调用一个与之相反的操作的方法就实现了撤消。比如加一个数后,保存此加数,让被加数送去加数,就实现一回撤消;如果实现多回撤消的话,把多次使用的加数保存到一个队列中,通过索引来得到某个加数,再用被减数减去它。
1 class Command 2 { 3 public abstract int Execute(int x); 4 public abstract int UnDo(int x); 5 } 6 class AddCommand : Command 7 { 8 private int value = 100;//传入的值和value相加操作 9 public override int Execute(int x) 10 { 11 return x + value; 12 } 13 public override int UnDo(int x) 14 { 15 return x - value; 16 } 17 }
什么时候可以用
系统需要把请求发送者和接收者解耦;系统需要撤消操作、或者把请求处理排队。
编程模式之15---行为型----命令模式