設計模式之----命令模式
轉載自:https://www.cnblogs.com/konck/p/4199907.html
雖然是.net 版的,不過不影響閱讀和學習。
經典的命令模式包括4個角色:
Command:定義命令的統一介面
ConcreteCommand:Command介面的實現者,用來執行具體的命令,某些情況下可以直接用來充當Receiver。
Receiver:命令的實際執行者
Invoker:命令的請求者,是命令模式中最重要的角色。這個角色用來對各個命令進行控制。
下面對上面四個角色的經典實現用程式碼來進行說明,這也是大部分文章對命令模式的運用方式。
經典程式碼實現:
1 /// <summary> 2 ///Command角色 3 /// </summary> 4 public interface ICommand 5 { 6 void Execute(); 7 } 8 9 /// <summary> 10 /// ConcreteCommand角色A 11 /// </summary> 12 public class ConcreteCommandA : ICommand 13 { 14 private Receiver receiver = null; 15 16 publicConcreteCommandA(Receiver receiver) 17 { 18 this.receiver = receiver; 19 } 20 21 public void Execute() 22 { 23 this.receiver.DoA(); 24 } 25 } 26 27 /// <summary> 28 /// ConcreteCommand角色B 29 /// </summary> 30 publicclass ConcreteCommandB : ICommand 31 { 32 private Receiver receiver = null; 33 34 public ConcreteCommandB(Receiver receiver) 35 { 36 this.receiver = receiver; 37 } 38 39 public void Execute() 40 { 41 this.receiver.DoB(); 42 } 43 } 44 45 /// <summary> 46 /// Receiver角色 47 /// </summary> 48 public class Receiver 49 { 50 public void DoA() 51 { 52 //DoSomething 53 } 54 55 public void DoB() 56 { 57 //DoSomething 58 } 59 } 60 61 /// <summary> 62 /// Invoker角色 63 /// </summary> 64 public class Invoker 65 { 66 private ICommand command = null; 67 68 //設定命令 69 public void SetCommand(ICommand command) 70 { 71 this.command = command; 72 } 73 //執行命令 74 public void RunCommand() 75 { 76 command.Execute(); 77 } 78 } 79 80 /// <summary> 81 /// 客戶端呼叫 82 /// </summary> 83 public class Client 84 { 85 public Client() 86 { 87 Receiver receiver = new Receiver(); 88 Invoker invoker = new Invoker(); 89 invoker.SetCommand(new ConcreteCommandA(receiver)); 90 invoker.RunCommand(); 91 invoker.SetCommand(new ConcreteCommandB(receiver)); 92 invoker.RunCommand(); 93 } 94 }
經典實現解析:
不知道大家看過上面的程式碼之後是什麼感覺,反正我看過上面的程式碼之後第一反應確實是越看越糊塗了,主要覺得有幾點疑問:
1. 執行命令可以,但是為什麼要用命令封裝起來,這不是有點脫褲子放屁的感覺麼?我完全可以這樣寫:
1 public class Client 2 { 3 public Client() 4 { 5 Receiver receiver = new Receiver(); 6 receiver.DoA(); 7 receiver.DoB(); 8 } 9 }
這樣不是更加簡單明瞭?兩個類搞定。
2. 通過繼承ICommand之後,增加命令怎麼增加?比如增加一個命令,要改動3個地方:增加一個ICommand實現,修改Receiver類,修改Client。這好像沒有對修改關閉啊?
更有甚者,有些地方採用下面這種方式寫Invoker類之後,要修改四個地方!
1 /// <summary> 2 /// Invoker角色 3 /// </summary> 4 public class Invoker 5 { 6 private ICommand commandA = null; 7 8 //設定命令A 9 public void SetCommandA(ICommand commandA) 10 { 11 this.commandA = commandA; 12 } 13 //執行命令A 14 public void RunCommandA() 15 { 16 commandA.Execute(); 17 } 18 19 private ICommand commandB = null; 20 21 //設定命令B 22 public void SetCommandB(ICommand commandB) 23 { 24 this.commandB = commandB; 25 } 26 //執行命令B 27 public void RunCommandB() 28 { 29 commandB.Execute(); 30 } 31 }
天啦!這簡直是個噩夢,完全沒感覺到對修改關閉啊。難道是我對命令模式完全理解錯誤了嗎?
3. ConcreteCommandA和ConcreteCommandB與Receiver類完全耦合了啊,要是有ConcreteCommandA與ConcreteCommandB要執行的命令在不同的Receiver中怎麼辦?
看到這裡,像我這種對設計模式一知半解的小夥伴估計完全懵了,在這話情況下對自己的智商產生了嚴重的懷疑,或者設計模式錯了???
但實際情況真的是這樣嗎?NONONONO,可以想到絕對不是,這可是奉為經典的設計模式啊,好吧,那我們來看看到底錯在哪裡:
1. 確實可以兩個類來搞定。但我們要牢記命令模式的初衷:對命令請求者(Invoker)和命令實現者(Receiver)的解耦,方便對命令進行各種控制。
打個比方:現在我們要對ConcreteCommandA與ConcreteCommandB以及其他一系列命令進行日誌記錄,並且兩個命令之間的操作間隔不能大於1秒。
這種情況下要直接用兩個類就會有大量的業務邏輯要在客戶端進行處理,當命令增加,對每個命令的控制增加時,就會在Client裡面產生大量的變化點,這樣耦合就出來了,但是採用命令模式之後,對著一系列的命令我們都可以進行控制,這就是對變化點的封裝,實際Invoker程式碼如下:
1 public class Invoker 2 { 3 private ICommand lastCommand = null; 4 private DateTime lastDateTime = DateTime.Now; 5 6 public void RunCommand(ICommand command) 7 { 8 //記錄操作日誌 9 Console.WriteLine(command.GetType().Name); 10 //大於1秒,執行命令 11 if (lastCommand == null || (DateTime.Now - this.lastDateTime).TotalSeconds > 1) 12 { 13 lastCommand = command; 14 lastDateTime = DateTime.Now; 15 command.Execute(); 16 } 17 //小於1秒時不執行,並進行相應處理 18 Console.WriteLine("操作間隔過短!"); 19 } 20 }
2. 增加命令:採用命令模式的時候,我感覺最大的耦合點變化到了Receiver和ConcreteCommand之間,當然我們可以對Receiver進行抽象,採用介面或者抽象類來封裝這個變化,但實際情況中我們會遇到多個命令來至於不同的Receiver,比如A,B兩個命令來至於ReceiverAB,C命令來至於ReceiverC,這種情況下我們怎麼應對命令的新增?對這種情況我的理解是命令模式並不能也不需要解決這個問題,因為命令模式的操作單元已經細化到了每一個具體的功能上面,當增加一個具體功能的時候是沒有很好的辦法對功能實現類進行修改關閉的(當然你可以把每個功能方法放到一個類中,但確實沒必要,這個粒度已經很小了),實際上也沒有必要的。
打個比方:一個介面有增加刪除功能,在一個類Receiver裡面實現了,現在要新增一個修改功能,有必要新增一個Reeciver類嗎?我自己的答案是沒有必要。
但是當業務是所有的功能都會同時修改時,我們就可以對這Receiver進行抽象,提取出IReceiver。
不同Receiver實現:
1 /// <summary> 2 /// ConcreteCommand角色A 3 /// </summary> 4 public class ConcreteCommandA : ICommand 5 { 6 private ReceiverAB receiver = null; 7 8 public ConcreteCommandA(ReceiverAB receiver) 9 { 10 this.receiver = receiver; 11 } 12 13 public void Execute() 14 { 15 this.receiver.DoA(); 16 } 17 } 18 19 /// <summary> 20 /// ConcreteCommand角色B 21 /// </summary> 22 public class ConcreteCommandB : ICommand 23 { 24 private ReceiverAB receiver = null; 25 26 public ConcreteCommandB(ReceiverAB receiver) 27 { 28 this.receiver = receiver; 29 } 30 31 public void Execute() 32 { 33 this.receiver.DoB(); 34 } 35 } 36 37 /// <summary> 38 /// ConcreteCommand角色C 39 /// </summary> 40 public class ConcreteCommandC : ICommand 41 { 42 private ReceiverC receiver = null; 43 44 public ConcreteCommandC(ReceiverC receiver) 45 { 46 this.receiver = receiver; 47 } 48 49 public void Execute() 50 { 51 this.receiver.DoC(); 52 } 53 } 54 55 /// <summary> 56 /// Receiver角色 57 /// </summary> 58 public class ReceiverAB 59 { 60 public void DoA() 61 { 62 //DoSomething 63 } 64 65 public void DoB() 66 { 67 //DoSomething 68 } 69 } 70 71 /// <summary> 72 /// Receiver角色 73 /// </summary> 74 public class ReceiverC 75 { 76 public void DoC() 77 { 78 //DoSomething 79 } 80 }
相同Receiver實現:
1 /// <summary> 2 /// Command角色 3 /// </summary> 4 public interface ICommand 5 { 6 void Execute(); 7 } 8 9 /// <summary> 10 /// ConcreteCommand角色A 11 /// </summary> 12 public class ConcreteCommandA : ICommand 13 { 14 private IReceiver receiver = null; 15 16 public ConcreteCommandA(IReceiver receiver) 17 { 18 this.receiver = receiver; 19 } 20 21 public void Execute() 22 { 23 this.receiver.DoA(); 24 } 25 } 26 27 /// <summary> 28 /// ConcreteCommand角色B 29 /// </summary> 30 public class ConcreteCommandB : ICommand 31 { 32 private IReceiver receiver = null; 33 34 public ConcreteCommandB(IReceiver receiver) 35 { 36 this.receiver = receiver; 37 } 38 39 public void Execute() 40 { 41 this.receiver.DoB(); 42 } 43 } 44 45 /// <summary> 46 /// ConcreteCommand角色B 47 /// </summary> 48 public class ConcreteCommandC : ICommand 49 { 50 private IReceiver receiver = null; 51 52 public ConcreteCommandC(IReceiver receiver) 53 { 54 this.receiver = receiver; 55 } 56 57 public void Execute() 58 { 59 this.receiver.DoA(); 60 } 61 } 62 63 public interface IReceiver 64 { 65 void DoA(); 66 67 void DoB(); 68 } 69 70 /// <summary> 71 /// Receiver角色 72 /// </summary> 73 public class ReceiverOne : IReceiver 74 { 75 public void DoA() 76 { 77 //DoSomething 78 } 79 80 public void DoB() 81 { 82 //DoSomething 83 } <