c#組合模式詳解

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

從接觸領域驅動設計的初學階段,到實現一個舊系統改造到DDD模型,再到按DDD規範落地的3個的項目。對於領域驅動模型設計研發,從開始的各種疑惑到吸收各種先進的理念,目前在技術實施這一塊已經基本比較成熟。在既往經驗中總結了一些在開發中遇到的技術問題和解決方案進行分享。 ...


基礎介紹:

  組合模式用於表示部分-整體層次結構。適用於希望用戶忽略組合對象與單個對象的不同,用戶將統一地使用組合結構中的所有對象的情況。

  顧名思義,什麼叫部分-整體,比如常見的前端UI,一個DIV標簽中可以存在多個A標簽、P標簽、DIV標簽等等。

  相較於DIV這個容器整體而言,其中所含的A標簽、P標簽甚至是DIV標簽都是單個的部分。

  而顯示的時候卻是一視同仁,不分部分還是整體。

  這就是典型的組合模式。

  再比如WinForms應用程式中,Label、TextBox等這樣簡單的控制項,可以理解為節點對象,它們中無法再插入其他控制項,它們就是最小的。

  而比如GroupBox、DataGrid這樣由多個簡單控制項組成的複合控制項或者容器,就可以理解為容器對象,它們中可以再插入其他的節點對象,甚至是再插入其他容器對象。

  但不管是Label這種節點對象還是DataGrid這種容器對象,想要顯示的話都需要執行OnPaint方法。

  為了表示這種對象之間整體與部分的層次結構,System.Windows.Forms.Control類就是應用了這種組合模式。

  這樣就可以簡單的把組合模式分為三個部分:

  • 抽象組件類(Component):它可以是介面或抽象類,為節點組件和容器組件對象聲明介面,在該類中包含共有行為的聲明。在抽象組件類中,定義了訪問及管理它的子組件的方法。
  • 節點組件類(Leaf):節點對象為最小組件(可以理解為樹葉),並繼承自抽象組件類,實現其共有聲明和方法。
  • 容器組件類(Composite):容器對象可以包含無數節點對象和無數容器組件(可以理解為樹枝,可以有無數樹葉或者分支),容器對象需要實現管理子對象的方法,如Add、Remove等。

應用場景:

  當發現需求中是體現部分與整體層次的結構時,以及你希望用戶可以忽略組合對象與單個對象的不同,統一地使用組合結構中的所有對象時,就應該考慮使用組合模式了

  UI的一系列控制項就是使用了組合模式,整體和部分可以被一致對待。

  組合模式有時候又叫做部分-整體模式,它使我們樹型結構的問題中,模糊了簡單元素和複雜元素的概念,客戶程式可以向處理簡單元素一樣來處理複雜元素,從而使得客戶程式與複雜元素的內部結構解耦。

  以下情況下適用Composite模式:

  1.對象的部分-整體層次結構。

  2.忽略組合對象與單個對象的不同,統一地使用組合結構中的所有對象。

創建方式:

 組合模式實現的最關鍵的地方是——簡單對象和複合對象必須實現相同的介面。這就是組合模式能夠將組合對象和簡單對象進行一致處理的原因。

  組合模式有兩種實現方式,一種是:透明式的組合模式,另外一種是:安全式的組合模式。

 

  透明方式————————————————

  Leaf葉類中也有Add 與 Remove方法,這種方式叫透明方式。

  也就是說在Component中聲明所有用來管理子對象的方法,其中包括Add、Remove等。

  這樣實現Component介面的所有子類都具備了Add與Remove。

  這樣做的好處是葉節點和枝節點對於外界沒有區別,它們具有一致的行為介面。

  但問題也很明顯,因為Leaf類本身不具備Add、Remove方法的功能,其實現是沒有意義的。

 

  安全方式————————————————

  在Component介面中不去聲明Add與Remove方法,那麼子類Leaf也就不用必須實現它們,而在Composite類中聲明所有用來管理子類對象的方法。
  
  

  以文檔管理器為例,文件夾為Composite,各類文檔為Leaf

  1. 透明方式

     1.抽象類

     1     /// <summary>
     2     /// 抽象組件類(Component)
     3     /// </summary>
     4     public abstract class DocumentComponent
     5     {
     6         public string Name { get; set; }
     7         protected List<DocumentComponent> mChildren;
     8         public List<DocumentComponent> Children
     9         {
    10             get { return mChildren; }
    11         }
    12         public DocumentComponent(string name)
    13         {
    14             this.Name = name;
    15             mChildren = new List<DocumentComponent>();
    16         }
    17 
    18         
    19         public abstract void AddChild(DocumentComponent document);
    20 
    21         public abstract void RemoveChild(DocumentComponent document);
    22 
    23         public abstract void Show();
    24     }

    介面或抽象類,為節點組件和容器組件對象聲明介面,在該類中包含共有行為的聲明。

    在抽象組件類中,定義了訪問及管理它的子組件的方法。

    本實例中Show為節點和容器組件共有方法,AddChildRemoveChild為容器組件方法。

    本類主要是為了讓節點類和容器類進行繼承方便統一管理

    2.節點組件類

     1     /// <summary>
     2     /// 節點組件類(Leaf),各類文檔,每類型可以添加一個對應類。
     3     /// </summary>
     4     public sealed class Word : DocumentComponent
     5     {
     6         public Word(string name)
     7             : base(name)
     8         { }
     9         public override void AddChild(DocumentComponent document)
    10         {
    11             throw new Exception("節點類不支持");
    12         }
    13 
    14         public override void RemoveChild(DocumentComponent document)
    15         {
    16             throw new Exception("節點類不支持");
    17         }
    18 
    19         public override void Show()
    20         {
    21             Console.WriteLine("這是一篇word文檔:" + Name);
    22         }
    23     }

    節點對象為最小組件(可以理解為樹葉),並繼承自抽象組件類,實現show方法。

    AddChildRemoveChild為容器組件方法,在節點類中拋出異常即可。

    該類是最小單位,沒有子節點。

    本類一個word文檔對象,如果有多個類型的文檔,可以聲明多個類。

    3.容器組件類

     1     /// <summary>
     2     /// 容器組件類(Composite),文件夾
     3     /// </summary>
     4     public class Folder : DocumentComponent
     5     {
     6         public Folder(string name)
     7             : base(name)
     8         { }
     9         public override void AddChild(DocumentComponent document)
    10         {
    11             mChildren.Add(document);
    12             Console.WriteLine("文檔或文件夾增加成功");
    13         }
    14         public override void RemoveChild(DocumentComponent document)
    15         {
    16             mChildren.Remove(document);
    17             Console.WriteLine("文檔或文件夾刪除成功");
    18         }
    19         public override void Show()
    20         {
    21             Console.WriteLine("這是一個文件夾:" + Name);
    22         }
    23     }

    容器對象可以包含無數節點對象和無數容器組件(可以理解為樹枝,可以有無數樹葉或者分支),容器對象需要實現管理子對象的方法,如AddChildRemoveChild等。

    本類是一個文件夾對象。

    4.客戶端

     1     /// <summary>
     2     /// 客戶端
     3     /// </summary>
     4     class Client
     5     {
     6         /// <summary>
     7         /// 廣度優先檢索
     8         /// </summary>
     9         /// <param name="component"></param>
    10         private static void BreadthFirstSearch(DocumentComponent component)
    11         {
    12             Queue<DocumentComponent> q = new Queue<DocumentComponent>();
    13             q.Enqueue(component);
    14             Console.WriteLine(component.Name);
    15             while (q.Count > 0)
    16             {
    17                 DocumentComponent temp = q.Dequeue();
    18                 List<DocumentComponent> children = temp.Children;
    19                 foreach (DocumentComponent child in children)
    20                 {
    21                     Console.WriteLine(child.Name);
    22                     q.Enqueue(child);
    23                 }
    24             }
    25         }
    26 
    27         /// <summary>
    28         /// 深度優先檢索
    29         /// </summary>
    30         /// <param name="component"></param>
    31         private static void DepthFirstSearch(DocumentComponent component)
    32         {
    33             Console.WriteLine(component.Name);
    34             List<DocumentComponent> children = component.Children;
    35             if (children == null || children.Count == 0) return;
    36             foreach (DocumentComponent child in children)
    37             {
    38                 DepthFirstSearch(child);
    39             }
    40         }
    41 
    42         static void Main(string[] args)
    43         {
    44             Console.WriteLine("創建三個目錄:");
    45             Folder folder = new Folder("根目錄");
    46             Folder folder1 = new Folder("子目錄1");
    47             Folder folder2 = new Folder("子目錄2");
    48 
    49             Console.WriteLine("\r\n創建兩個文檔:");
    50             Word word1 = new Word("word文檔1");
    51             Word word2 = new Word("word文檔2");
    52 
    53             Console.WriteLine("\r\n將子目錄1添加到根目錄下:");
    54             folder.AddChild(folder1);
    55             Console.WriteLine("\r\n將子目錄2添加到子目錄1下:");
    56             folder1.AddChild(folder2);
    57 
    58             Console.WriteLine("\r\n將word文檔1添加到子目錄2下:");
    59             folder2.AddChild(word1);
    60             Console.WriteLine("\r\n將word文檔2添加到根目錄下:");
    61             folder.AddChild(word2);
    62 
    63             Console.WriteLine("\r\n廣度優先列表:");
    64             DepthFirstSearch(folder);
    65             Console.WriteLine("\r\n深度優先列表:");
    66             BreadthFirstSearch(folder);
    67 
    68             Console.ReadKey();
    69         }
    70 
    71 
    72     }

    註:BreadthFirstSearch為廣度優先檢索,依次列出所有元素。DepthFirstSearch為深度優先檢索,列舉完一個文件夾後,返回根目錄繼續列舉其他文件夾。

    通過上述實例可以看出,文件夾可以創建N個子文件夾,但文檔只能放在文件夾中,無法放在另一個文檔中。

  2. 安全方式

      1     /// <summary>
      2     /// 抽象組件類(Component)
      3     /// </summary>
      4     public abstract class DocumentComponent
      5     {
      6         public string Name { get; set; }
      7         protected List<DocumentComponent> mChildren;
      8         public List<DocumentComponent> Children
      9         {
     10             get { return mChildren; }
     11         }
     12         public DocumentComponent(string name)
     13         {
     14             this.Name = name;
     15             mChildren = new List<DocumentComponent>();
     16         }
     17 
     18         public abstract void Show();
     19     }
     20 
     21     /// <summary>
     22     /// 節點組件類(Leaf),各類文檔,每類型可以添加一個對應類。
     23     /// </summary>
     24     public sealed class Word : DocumentComponent
     25     {
     26         public Word(string name)
     27             : base(name)
     28         { }
     29 
     30         public override void Show()
     31         {
     32             Console.WriteLine("這是一篇word文檔:" + Name);
     33         }
     34     }
     35 
     36     /// <summary>
     37     /// 容器組件類(Composite),文件夾
     38     /// </summary>
     39     public class Folder : DocumentComponent
     40     {
     41         public Folder(string name)
     42             : base(name)
     43         { }
     44         public void AddChild(DocumentComponent document)
     45         {
     46             mChildren.Add(document);
     47             Console.WriteLine("文檔或文件夾增加成功");
     48         }
     49         public void RemoveChild(DocumentComponent document)
     50         {
     51             mChildren.Remove(document);
     52             Console.WriteLine("文檔或文件夾刪除成功");
     53         }
     54         public override void Show()
     55         {
     56             Console.WriteLine("這是一個文件夾:" + Name);
     57         }
     58     }
     59 
     60 
     61     /// <summary>
     62     /// 客戶端
     63     /// </summary>
     64     class Client
     65     {
     66         /// <summary>
     67         /// 廣度優先檢索
     68         /// </summary>
     69         /// <param name="component"></param>
     70         private static void BreadthFirstSearch(DocumentComponent component)
     71         {
     72             Queue<DocumentComponent> q = new Queue<DocumentComponent>();
     73             q.Enqueue(component);
     74             Console.WriteLine(component.Name);
     75             while (q.Count > 0)
     76             {
     77                 DocumentComponent temp = q.Dequeue();
     78                 List<DocumentComponent> children = temp.Children;
     79                 foreach (DocumentComponent child in children)
     80                 {
     81                     Console.WriteLine(child.Name);
     82                     q.Enqueue(child);
     83                 }
     84             }
     85         }
     86 
     87         /// <summary>
     88         /// 深度優先檢索
     89         /// </summary>
     90         /// <param name="component"></param>
     91         private static void DepthFirstSearch(DocumentComponent component)
     92         {
     93             Console.WriteLine(component.Name);
     94             List<DocumentComponent> children = component.Children;
     95             if (children == null || children.Count == 0) return;
     96             foreach (DocumentComponent child in children)
     97             {
     98                 DepthFirstSearch(child);
     99             }
    100         }
    101 
    102         static void Main(string[] args)
    103         {
    104             Console.WriteLine("創建三個目錄:");
    105             Folder folder = new Folder("根目錄");
    106             Folder folder1 = new Folder("子目錄1");
    107             Folder folder2 = new Folder("子目錄2");
    108 
    109             Console.WriteLine("\r\n創建兩個文檔:");
    110             Word word1 = new Word("word文檔1");
    111             Word word2 = new Word("word文檔2");
    112 
    113             Console.WriteLine("\r\n將子目錄1添加到根目錄下:");
    114             folder.AddChild(folder1);
    115             Console.WriteLine("\r\n將子目錄2添加到子目錄1下:");
    116             folder1.AddChild(folder2);
    117 
    118             Console.WriteLine("\r\n將word文檔1添加到子目錄2下:");
    119             folder2.AddChild(word1);
    120             Console.WriteLine("\r\n將word文檔2添加到根目錄下:");
    121             folder.AddChild(word2);
    122 
    123             Console.WriteLine("\r\n廣度優先列表:");
    124             DepthFirstSearch(folder);
    125             Console.WriteLine("\r\n深度優先列表:");
    126             BreadthFirstSearch(folder);
    127 
    128             Console.ReadKey();
    129         }
    130 
    131     }

    從上述實例中可以看出,安全模式其實就是把共有的方法放在抽象類的。

    文件夾獨有的方法放在容器類中,這樣做保證了節點類就沒有Add和Remove等無用方法。

總結:

  組合模式解耦了客戶程式與複雜元素內部結構,從而使客戶程式可以向處理簡單元素一樣來處理複雜元素。

  

  

作者:少年真愛 出處:https://www.cnblogs.com/mingnianjiehunba/p/17735739.html 博主的文章沒有高度、深度和廣度,只是湊字數。由於博主的水平不高,不足和錯誤之處在所難免,希望大家能夠批評指出。 博主是利用讀書、參考、引用、抄襲、複製和粘貼等多種方式打造成自己的文章,請原諒博主成為一個無恥的文檔搬運工!
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在之前的實時湖倉系列文章中,我們已經介紹了實時湖倉對於當前企業數字化轉型的重要性,實時湖倉的功能架構設計,以及實時計算和數據湖結合的應用場景。 在本篇文章中,將介紹袋鼠雲數棧在構建實時湖倉系統上的探索與落地實踐,及未來規劃。 數棧為什麼選擇實時湖倉 數棧作為一個數據開發平臺,在未引入實時湖倉之前提供 ...
  • 資料庫底層實現博大精深,本文所述,根據線上場景進行了一些研究和探討,希望能為相關場景提供一些啟示。文章中難免會有不足之處,希望讀者能給予寶貴的意見和建議 ...
  • 前言 近期接到一個關於谷歌EDLA認證的需求,我負責的是谷歌原生桌面佈局的修改,通過研究源碼,將涉及到了一些修改思路發出來,大家可以參考一下有沒有對你有用的信息。主要修改內容有: 1、搜索欄、底部導航欄未居中 2、中部應用未按要求排布,詳情請參考摹客 3、在原生Google桌面未添加中性S-writ ...
  • 使用不可信的數據,通過調用不安全的遞歸函數來暴露預設原型 原型污染:基礎 什麼是原型污染? 原型污染是一種針對JavaScript運行時的註入攻擊。通過原型污染,攻擊者可以控制對象屬性的預設值,從而篡改應用程式的邏輯並可能導致服務被拒絕,甚至在某些極端情況下遠程執行代碼。 現在,你是不是滿腦子充滿了 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 記錄一下在公司遇到的一些功能,以及相關實現 以上的內容我花了一周時間去實現的,自己也覺得時間很長,但主要因為很少使用ECharts,導致使用的過程中大部分的時間都在查文檔。 對於上面的這些功能點,其實算是寫了兩遍吧,這周一開了個Code ...
  • 1、業務背景 使用vue+element開發報表功能時,需要列表上某列的超鏈接按鈕彈窗展示,在彈窗的el-table列表某列中再次使用超鏈接按鈕點開彈窗,以此類推多表格彈窗嵌套,本文以彈窗兩次為例 最終效果如下示例頁面 2、具體實現和問題拋出 <template> <div class="el_ma ...
  • 作者:WangMin 格言:努力做好自己喜歡的每一件事 對於初學CSS的同學來說,會有很多屬性相關的疑問,行高屬性 line-height一定是其中一個,因為它是CSS中非常重要的一個屬性,這個屬性改變元素在網頁中的行高,讓你的網頁看起來更加整潔,美觀。 什麼是行高? CSS中的行高(line-he ...
  • 我們可以採取多種方法對數據架構進行分類,且每種方法都有自己的優缺點。它們可以幫助你做出明智的決定,選擇適合與你需求最匹配的設計。兩種最流行的基於速度的架構是Lambda和Kappa,本文將介紹基於速度的數據架構,以及它們在總體方案中的位置。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...