c#中命令模式詳解

来源:https://www.cnblogs.com/mingnianjiehunba/archive/2023/11/16/17832005.html
-Advertisement-
Play Games

需求落地分散式應用服務 將需求轉化為分散式應用服務的過程可以按照以下步驟進行: 理解需求:首先,你需要仔細閱讀和理解業務需求。與相關的利益相關者(如業務分析師、產品經理等)進行溝通,確保你對需求的理解是準確的。 設計架構:根據需求,設計一個適合的分散式應用架構。這包括確定應用的組件和模塊,以及它們之 ...


基本介紹: 

  命令模式,顧名思義就是將命令抽象化,然後將請求者和接收者通過命令進行綁定。

  而命令的請求者只管下達命令,命令的接收者只管執行命令。

  從而實現瞭解耦,請求者和接受者二者相對獨立。

  單獨理解起來比較困難,咱們還是通過具體實例來說明吧。

舉例說明:

  生活中遙控控制電器就是命令模式,比如智能開關控制一盞燈。

  首先將燈的開和關封裝成一個命令,然後將這個命令綁定到智能開關上。

  智能開關的執行其實就是燈開關的執行,燈和智能開關是通過命令來進行交互的。

  這個時候如果想控制電視的開關,那就可以將電視的開關封裝成一個命令。

  然後將這個命令綁定到智能開關上,智能開關的執行這個時候就變成了電視的開關。

  如果即想控制電視又想控制燈,那就可以將燈的命令和電視的命令都綁定到智能開關上。

 

  又比如皇帝寫了一道聖旨,命令張三即刻回京。

  聖旨跟張三是綁定關係,也就是說張三是實際執行者,聖旨是命令,起到的作用就是跟張三進行綁定和傳達信息。

  那想個問題傳達聖旨的宦官是誰有關係嗎,是沒有的,是誰都可以。但又是不可或缺的。它就相當於一個遙控或者說是中間人、傳話人。

  是皇帝將這個宦官和聖旨進行了綁定(客戶端就是那個皇帝)。那在想個問題,是先有聖旨再有張三呢,還是先有張三再有聖旨。

  答案是肯定的,先有張三,也就是說得先有具體執行者。

  然後再有聖旨即命令,通過名字將張三和聖旨綁定到一起。

  如果皇帝想命令李四也回京,那這個聖旨可以命令李四嗎?是不可以的,需要重新寫一份聖旨。

  所以從這個不難看出,命令類和具體的執行者是強綁定關係,當然也可以做成工廠。

  這個時候大家想個問題,兩道聖旨是不是可以讓同一個宦官去傳達就可以。因為宦官和執行者不存在綁定,它只和聖旨綁定。

  所以說宦官就相當於遙控,它只需要宣讀聖旨而不必在意具體是怎麼執行的,就可以讓不同的執行人進行不同的操作。

  這就是命令模式的本質。

基本結構:

  通過上述例子不難看出,命令模式主要核心構件有以下幾個:

  ◊ 命令的執行者(接收者Receiver):它單純的只具體實現了功能。(實例中對應的則是張三和李四)

  ◊ 命令的下達者(調用者Invoker)

    就是起到遙控的作用,首先在類中聲明抽象命令類的引用,並通過參數註入等方式進行實例化。

    然後通過調用命令對象來告訴執行者執行功能。起到一個傳聲筒的作用。(實例中對應的是宦官)

  ◊ 抽象命令類Command

    主要作用有兩點,其一就是為了規範具體命令類,聲明其執行方法。

    其二就是為了方便調用者Invoker調用,使其不需要知道具體命令類,只需要執行抽象命令類即可。(實例中對應的是泛指聖旨)

  ◊ 具體命令類ConcreteCommand

    繼承自抽象類,在類中首先聲明瞭執行者的引用,通過參數註入等方式進行創建執行者對象,將命令類和執行者進行綁定。

    其次具體實現了抽象類中聲明的執行方法,調用執行者對象中方法進行執行。(實例中對應的是張三和李四具體的兩道聖旨)

  ◊ 客戶端角色Client

    主要是負責將執行者和命令類進行綁定,其次將命令類和調用者進行綁定。

    使其調用者可以通過命令類給執行者傳遞消息進行執行。(實例中對應的是皇帝角色)

優缺點:

  優點

    1.降低了系統的耦合性。將調用操作對象和知道如何實現該操作對象的解耦。

    2.通過命令模式,新的命令可以很輕鬆的加入系統中,且不會影響其他類,符合開閉原則。

  缺點

    使用命令模式可能會導致某些系統存在過多的命令類,會影響使用。

  命令模式適用情形

    1.將用戶界面和行為分離。

    2.對請求進行排隊,記錄請求日誌以及支持可撤銷的操作等。

具體實例:

  就以皇帝下達聖旨作為實例吧,首先先說下順序。

    1.得先有執行者也就是接收聖旨的人,它主要是執行具體的行為和功能,如果是遙控控制燈,那這裡就是那盞燈本身。這裡命名為Receiver_ZhangSan類。

    2.其次就需要創建聖旨,也就是傳達命令的媒介。但每道聖旨都是一樣的材質和規則,所以需要先創建一個抽象類來規範。這裡將抽象類命名為Command類,將具體命令類即聖旨內容命名為ConcreteCommand類它繼承自抽象類。

    3.有了執行人也有了聖旨,那就可以安排一個傳旨的人就可以了(如果是遙控控制燈,這裡相當於遙控)。這裡命名為Invoker類。

    4.執行人有了,聖旨有了,傳旨的人也有了,那皇帝就可以下令執行了。這裡就相當於客戶端調用了。

  實現方式說明:

    1.創建Receiver_ZhangSan類,設置具體執行方法,這裡可以是騎馬回京,可以是其他的操作。它只負責具體功能的實現。

    2.創建Command抽象類,聲明Execute方法。

    3.創建ConcreteCommand命令類,繼承自抽象類。在該類中創建Receiver_ZhangSan類的引用,並通過參數註入或屬性賦值等方式實例化該類,併在Execute方法中具體調用該類中的具體實現方法。

    4.創建Invoker類,在該類中創建Command抽象類的引用,並通過參數註入或屬性賦值等方式將ConcreteCommand類實例化,併在該類中創建方法來執行抽象類中的Execute方法。

    5.客戶端直接調用Invoker類中的方法來執行命令。

    具體的執行過程就是,通過Invoker類中的方法來調用抽象類中的Execute方法其實就是調用ConcreteCommand中的Execute方法,

    然後ConcreteCommand中的Execute調用的又是Receiver_ZhangSan類中的具體實現方法。

    這樣就相當於通過Invoker類調用了Receiver_ZhangSan類中的方法,只不過是中間加了一個傳話人即命令類。

  為什麼這麼做呢?這樣做有什麼好處呢?

    好處也很好理解,那就是Invoker類不必知道具體執行人是誰,也不必知道具體怎麼執行,只需要將命令註入給該類就好了。

    這樣如果命令改變了,那直接改變註入的命令類就可以了,方便快捷,而且不需要修改現有的類,符合開閉原則。

    而且還可以按順序批量執行命令,只需要將命令依次註入給Invoker類進行調用就可以了。即任務隊列。

    也可以一次性註入多條命令,同時執行。

 1     /// <summary>
 2     /// 張三類,它是實際執行者,也就是命令接收者
 3     /// </summary>
 4     public class Receiver_ZhangSan
 5     {
 6         private string strName = "張三";
 7         public void Eat()
 8         {
 9             Console.WriteLine(strName + ":吃飯。");
10         }
11         public void Behavior1()
12         {
13             Console.WriteLine(strName + ":騎馬回京。");
14         }
15         public void Behavior2()
16         {
17             Console.WriteLine(strName + ":原地待命。");
18         }
19     }
20 
21     /// <summary>
22     /// 命令抽象類,規範命令類,並提供執行方法。
23     /// </summary>
24     public abstract class Command
25     {
26         //執行方法
27         public abstract void Execute();
28     }
29 
30     /// <summary>
31     /// 具體命令類,也就是聖旨書寫的具體內容。
32     /// </summary>
33     public class ConcreteCommand1 : Command
34     {
35         //創建執行人的引用,這裡使用readonly,規定只可以在構造函數中進行賦值。
36         private readonly Receiver_ZhangSan _Receiver_ZhangSan;
37 
38         //構造函數,參數註入方式,將執行人註入到命令類中。好比是將人名寫在聖旨上。
39         public ConcreteCommand1(Receiver_ZhangSan receiver_ZhangSan)
40         {
41             this._Receiver_ZhangSan = receiver_ZhangSan;
42         }
43 
44         //具體命令
45         public override void Execute()
46         {
47             //下達立刻回京的命令
48             this._Receiver_ZhangSan.Behavior1();
49         }
50     }
51 
52     /// <summary>
53     /// 調用者類,命令下達者,即宦官。
54     /// </summary>
55     public class Invoker
56     {
57         //創建抽象命令類的引用,這裡不同於具體命令類,方便演示,使用了參數賦值的形式進行註入。
58         public Command command { get; set; }
59 
60         //下達命令,即宣讀聖旨
61         public void Reading()
62         {
63             if (command != null)
64             {
65                 command.Execute();
66             }
67         }
68     }
69 
70     /// <summary>
71     /// 客戶端
72     /// </summary>
73     class Client
74     {
75         static void Main(string[] args)
76         {
77             //首先創建接收人,即張三。
78             Receiver_ZhangSan receiver_ZhangSan = new Receiver_ZhangSan();
79 
80             //然後創建命令類,即聖旨。這裡通過構造函數參數註入的形式將張三和命令進行綁定。
81             Command command = new ConcreteCommand1(receiver_ZhangSan);
82 
83             //其次創建調用者,即宦官。
84             Invoker invoker = new Invoker();
85             //為了演示不同註入方式,這裡通過屬性賦值的方式將命令和調用者綁定。
86             invoker.command = command;
87 
88             //調用,即宣讀聖旨
89             invoker.Reading();
90 
91             Console.WriteLine("\r\n");
92 
93             Console.ReadKey();
94         }
95     }

  如果皇帝想讓張三吃飽飯再回京,同時想讓李四原地待命該如何實現呢。

  兩種方式,如果張三和李四在一起,就可以寫在一個聖旨里即寫在一個命令類里。

  如果不在一起,那就需要寫兩道聖旨,即兩個命令類。

添加李四類:

 1     /// <summary>
 2     /// 李四類,它是實際執行者,也就是命令接收者
 3     /// </summary>
 4     public class Receiver_LiSi
 5     {
 6         private string strName = "李四";
 7         public void Eat()
 8         {
 9             Console.WriteLine(strName + ":吃飯。");
10         }
11         public void Behavior1()
12         {
13             Console.WriteLine(strName + ":坐馬車回京。");
14         }
15         public void Behavior2()
16         {
17             Console.WriteLine(strName + ":原地待命。");
18         }
19     }

添加新命令類:

 1     /// <summary>
 2     /// 聖旨,即命令類
 3     /// </summary>
 4     public class ConcreteCommand3 : Command
 5     {
 6         //創建執行人的引用
 7         private readonly Receiver_LiSi _Receiver_LiSi;
 8         private readonly Receiver_ZhangSan _Receiver_ZhangSan;
 9 
10         //將執行人註入到命令類中。
11         public ConcreteCommand3(Receiver_LiSi receiver_LiSi, Receiver_ZhangSan receiver_ZhangSan)
12         {
13             this._Receiver_LiSi = receiver_LiSi;
14             this._Receiver_ZhangSan = receiver_ZhangSan;
15         }
16 
17         //具體命令
18         public override void Execute()
19         {
20             //讓張三吃飽飯再回京
21             this._Receiver_ZhangSan.Eat();
22             this._Receiver_ZhangSan.Behavior1();
23             //讓李四原地待命
24             this._Receiver_LiSi.Behavior2();
25         }
26     }

客戶端調用:

 1     /// <summary>
 2     /// 客戶端
 3     /// </summary>
 4     class Client
 5     {
 6         static void Main(string[] args)
 7         {
 8             //首先創建接收人,即張三和李四。
 9             Receiver_ZhangSan receiver_ZhangSan = new Receiver_ZhangSan();
10             Receiver_LiSi receiver_LiSi = new Receiver_LiSi();
11 
12             //然後創建命令類,即聖旨。這裡通過構造函數參數註入的形式將張三和命令進行綁定。
13             Command command = new ConcreteCommand3(receiver_LiSi, receiver_ZhangSan);
14 
15             //其次創建調用者,即宦官。
16             Invoker invoker = new Invoker();
17             //為了演示不同註入方式,這裡通過屬性賦值的方式將命令和調用者綁定。
18             invoker.command = command;
19 
20             //調用,即宣讀聖旨
21             invoker.Reading();
22 
23             Console.WriteLine("\r\n");
24 
25             Console.ReadKey();
26         }
27     }

   以上實例則是張三和李四在一起,寫在一個聖旨里即寫在一個命令類里。如果不在一起呢?

創建新命令類:

 1     /// <summary>
 2     /// 讓李四原地待命的聖旨
 3     /// </summary>
 4     public class ConcreteCommand2 : Command
 5     {
 6         //創建執行人的引用
 7         private readonly Receiver_LiSi _Receiver_LiSi;
 8 
 9         //將執行人註入到命令類中。
10         public ConcreteCommand2(Receiver_LiSi receiver_LiSi)
11         {
12             this._Receiver_LiSi = receiver_LiSi;
13         }
14 
15         //具體命令
16         public override void Execute()
17         {
18             //讓李四原地待命
19             this._Receiver_LiSi.Behavior2();
20         }
21     }

客戶端調用:

 1     /// <summary>
 2     /// 客戶端
 3     /// </summary>
 4     class Client
 5     {
 6         static void Main(string[] args)
 7         {
 8             //首先創建接收人,即張三和李四。
 9             Receiver_ZhangSan receiver_ZhangSan = new Receiver_ZhangSan();
10             Receiver_LiSi receiver_LiSi = new Receiver_LiSi();
11 
12             //然後創建命令類,即聖旨1
13             Command command1 = new ConcreteCommand1(receiver_ZhangSan);
14             //然後創建命令類,即聖旨2
15             Command command2 = new ConcreteCommand2(receiver_LiSi);
16 
17             //其次創建調用者,即宦官。
18             Invoker invoker = new Invoker();
19             invoker.command = command1;
20             invoker.Reading();
21 
22             invoker.command = command2;
23             invoker.Reading();
24 
25             Console.WriteLine("\r\n");
26 
27             Console.ReadKey();
28         }
29     }

   另一種方式,可以在Invoker里申明數組保存命令依次執行。

 1     /// <summary>
 2     /// 調用者類,命令下達者,即宦官。
 3     /// </summary>
 4     public class InvokerList
 5     {
 6         public List<Command> commands;
 7         public void AddCommand(Command command)
 8         {
 9             if (commands == null)
10             {
11                 commands = new List<Command>();
12             }
13             commands.Add(command);
14         }
15 
16         //下達命令,即宣讀聖旨
17         public void Reading()
18         {
19             if (commands != null)
20             {
21                 foreach (Command item in commands)
22                 {
23                     item.Execute();
24                 }
25             }
26         }
27     }

客戶端調用:

 1     /// <summary>
 2     /// 客戶端
 3     /// </summary>
 4     class Client
 5     {
 6         static void Main(string[] args)
 7         {
 8             //首先創建接收人,即張三和李四。
 9             Receiver_ZhangSan receiver_ZhangSan = new Receiver_ZhangSan();
10             Receiver_LiSi receiver_LiSi = new Receiver_LiSi();
11 
12             //然後創建命令類,即聖旨1
13             Command command1 = new ConcreteCommand1(receiver_ZhangSan);
14             //然後創建命令類,即聖旨2
15             Command command2 = new ConcreteCommand2(receiver_LiSi);
16 
17             //其次創建調用者,即宦官。
18             InvokerList invoker = new InvokerList();
19             invoker.AddCommand(command1);
20             invoker.AddCommand(command2);
21             invoker.Reading();
22 
23             Console.WriteLine("\r\n");
24 
25             Console.ReadKey();
26         }
27     }

從以上事例中不難看出,命令的調用者即Invoker類無需知道執行人和具體執行什麼內容,更換命令也很方便,只需要改變註入的命令類就可以了。

但也可以從實例中看出,新增一個命令就需要新增一個命令類,這可能會導致某些系統存在過多的命令類,會影響使用。

  讀後思考:書讀百遍其義自見,讀不夠百遍,那不如自己動手寫寫,不如就拿遙控控制不同電器為例子。

  友情提示:
      創建一盞燈
      創建一個命令,該命令控制繼承自抽象類,執行燈的一些操作,所以需要將創建好的燈註入到命令對象里。
      創建一個遙控,將遙控的某一個按鈕綁定上該命令。可以是參數註入,也可以是屬性賦值。
      具體執行就是執行遙控的函數,然後傳遞到命令類 最後才是具體燈的操作。

      關鍵點就是燈跟命令進行綁定,方式就是在命令里聲明一個燈的引用,然後參數註入等方式進行實例化。
      然後命令再跟遙控進行綁定,方式相同,在遙控里聲明一個命令的引用,然後參數註入等方式進行實例化。
      最後實行執行操作的是遙控。

      為什麼要將命令抽象化,因為要跟遙控綁定,遙控只是執行命令,具體是什麼樣的命令,就交給命令本身去決定了。
      還有一個問題就是,有多少個電器 就需要多少個命令 。那其實可以合併成一個。大家可以動手試試。

總結:

  將一個請求封裝為一個對象,從而使你可用不同的請求對客戶進行參數化。適用於需要將請求調用者和請求接收者解耦,使得調用者和接收者不直接交互。

  命令模式的實現要點在於把某個具體的命令抽象化為具體的命令類,並通過加入命令請求者角色來實現將命令發送者對命令執行者的依賴分割開。

  命令模式的目的是解除命令發出者和接收者之間的緊密耦合關係,使二者相對獨立,有利於程式的並行開發和代碼的維護。

  命令模式的核心思想是將請求封裝為一個對象,將其作為命令發起者和接收者的中介,而抽象出來的命令對象又使得能夠對一系列請求進行操作,如對請求進行排隊,記錄請求日誌以及支持可撤銷的操作等。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 目錄準備界面:view控制項LayoutCreator事件監聽OnClickListener轉跳頁面IntentIntent傳遞數據Toast和AlertDialogGson使用OKhttp3的基本使用post方法get方法輕量級存儲SharedPreferenceListView基本使用1、Simp ...
  • 作為一名全棧工程師,在日常的工作中,可能更側重於後端開發,如:C#,Java,SQL ,Python等,對前端的知識則不太精通。在一些比較完善的公司或者項目中,一般會搭配前端工程師,UI工程師等,來彌補後端開發的一些前端經驗技能上的不足。但並非所有的項目都會有專職前端工程師,在一些小型項目或者初創公... ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 在我們寫項目代碼時,應該更加專註於業務邏輯的實現,而把定式代碼交給js庫或工程化自動處理,而我想說的是,請求邏輯其實也是可以繼續簡化的。 你可能會說,用axios或fetch api就夠了啊,哪有什麼請求邏輯,那可能是你還沒有意識到這個問 ...
  • 需求:客戶的電腦都只能訪問內,伺服器可以訪問外網,客戶電腦使用的項目中用到了高德webapi2.0。10.200.31.45:32100是我們的web伺服器。 網上基本上都是對高德webapi1.4的配置方式,而web2.0有一些差別。 1.前端修改高德地圖的js應用 如果是index.html引入 ...
  • 最近做的幾個項目經常遇到這樣的需求,要在表格上增加一個自定義表格欄位設置的功能。就是用戶可以自己控制那些列需要展示。在幾個項目里都實現了一遍,每個項目的需求又都有點兒不一樣,迭代了很多版,所以抽時間把這個功能封裝了個組件:@silverage/table-custom,將這些差別都集成了進去,方便今... ...
  • 大綱 本文內容更多的是講講使用 vuex 的一些心得想法,所以大概會講述下麵這些點: Q1:我為什麼會想使用 vuex 來管理數據狀態交互? Q2:使用 vuex 框架有哪些缺點或者說副作用? Q3:我是如何在項目里使用 vuex 的? 初識 vuex 對於 vuex,有人喜歡,有人反感 喜歡的人覺 ...
  • 微服務是一種軟體架構策略,將應用程式分解為一組解耦的、自治的服務。採用微服務架構將改善整體性能和可擴展性,本文將概述微服務設計和實施的基本考慮因素。 ...
  • 重構有利於項目的健壯和精簡,平時要養成重構的好習慣,“小步快走”,儘量避免留著統一重構的思想,積累很多技術債後重構精力、時間成本很大,風險也會大很多 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...