【設計模式】組合模式 Composite Pattern

来源:https://www.cnblogs.com/vaiyanzi/archive/2018/08/20/9503625.html
-Advertisement-
Play Games

樹形結構是軟體行業很常見的一種結構,幾乎隨處可見, 比如: HTML 頁面中的DOM,產品的分類,通常一些應用或網站的菜單,Windows Form 中的控制項繼承關係,Android中的View繼承關係,部門的組織架構,Windows 資源管理器 等等都是樹形結構。 Windows 資源管理 樹形結 ...


樹形結構是軟體行業很常見的一種結構,幾乎隨處可見,  比如: HTML 頁面中的DOM,產品的分類,通常一些應用或網站的菜單,Windows Form 中的控制項繼承關係,Android中的View繼承關係,部門的組織架構,Windows 資源管理器 等等都是樹形結構。

image

 

 

 

 

 

 

 

 

 

 

 

 

Windows 資源管理

樹形結構是很有特點的一種數據結構,  下圖是一棵樹:

image

樹結構有幾個術語:

根節點:最高的節點被稱為根節點,上圖中的紅色節點是根節點。根節點沒有父節點。

父節點:如果一個節點的下麵鏈接著其它節點那上層節點被稱作該節點的父節點,下層節點被稱作父節點的子節點。除了根節點外子節點有且只有一個父節點。上圖中的紅色和黃色都是父節點。

葉子節點:每一個節點有0個或多個子節點。有0個(沒有)子節點的節點稱作葉子節點。上圖中的綠色節點都是葉子節點。

如果要給樹形結構上增加或者刪除一些節點該如何處理呢? 組合模式提供了面向對象優雅的處理這種數據結構的方法。

一、組合模式的定義

組合模式(Composite Pattern):組合多個對象形成樹形結構以表示具有“整體—部分”關係的層次結構。組合模式對單個對象(即葉子對象)和組合對象(即容器對象)的使用具有一致性,組合模式又可以稱為“整體—部分”(Part-Whole)模式,它是一種對象結構型模式。

二、組合模式結構圖

image

組合模式結構圖

1、Component(抽象構件):

它可以是介面或抽象類,為葉子構件和容器構件對象聲明介面,在該角色中可以包含所有子類共有行為的聲明和實現。在抽象構件中定義了訪問及管理它的子構件的方法,如增加子構件、刪除子構件、獲取子構件等。

2 、Composite(容器構件):

它在組合結構中表示容器節點對象,容器節點包含子節點,其子節點可以是葉子節點,也可以是容器節點,它提供一個集合用於存儲子節點,實現了在抽象構件中定義的行為,包括那些訪問及管理子構件的方法,在其業務方法中可以遞歸調用其子節點的業務方法。

3、Leaf(葉子構件):

它在組合結構中表示葉子節點對象,葉子節點沒有子節點,它實現了在抽象構件中定義的行為。對於那些訪問及管理子構件的方法,可以通過異常等方式進行處理。

三、組合模式的示例代碼

public abstract class Component
{
    public abstract void Operaton(int depth);
    public abstract void Add(Component component);
    public abstract void Remove(Component component);
    public abstract Component GetChild(int i);
}

public class Composite : Component
{
    private IList<Component> components = new List<Component>();
    public override void Operaton(int depth)
    {
        Console.WriteLine(new String(' ', depth - 2) + "|");
        Console.WriteLine(new String(' ', depth - 2) + "--" + this.GetType().Name + " Opration");
        foreach (var component in components)
        {
            component.Operaton(depth + 2);
        }
    }

    public override void Add(Component component)
    {
        components.Add(component);
    }

    public override void Remove(Component component)
    {
        components.Remove(component);
    }

    public override Component GetChild(int i)
    {
        return components[i];
    }
}
public class Leaf : Component
{
    public override void Operaton(int depth)
    {
        Console.WriteLine(new String(' ', depth - 2) + "|");
        Console.WriteLine(new String(' ', depth - 2) + "--" + this.GetType().Name + " Opration");
    }

    public override void Add(Component component)
    {
        Console.WriteLine("Cannot add to a leaf");
    }

    public override void Remove(Component component)
    {
        Console.WriteLine("Cannot remove from a leaf");
    }

    public override Component GetChild(int i)
    {
        throw new Exception("Cannot get a child from a leaf");

    }
}

客戶端調用:

static void Main(string[] args)
{
    Component root = new Composite.Structure.Composite();
    root.Add(new Leaf());
    root.Add(new Leaf());
            
    var composite = new Composite.Structure.Composite();
    composite.Add(new Leaf());
    composite.Add(new Leaf());
           
    root.Add(composite);

    var componsite2 = new Composite.Structure.Composite();
    composite.Add(componsite2);

    componsite2.Add(new Leaf());
    componsite2.Add(new Leaf());
           
    root.Operaton(2);
    Console.ReadKey();
}

  輸出結果

image

四、組合模式實例

現在有一個新聞系統,這個新聞系統里有若幹類別,每一個子類里又包含若幹分類,理論上是一個無限級的樹形結構,同時每個分類上都可能包含具體的新聞。結構如下:

image 新聞系統分類組織機構圖

上圖中的白色部分表示分類(Category),綠色部分表示新聞(News), 現在要添加新聞的分類,刪除新聞,以及將新聞展示出來。下麵我們用組合模式來實現這些功能.

通過分析,可以提出構件 NewsComponent, News 和Category 三個對象,NewsComponent相當於組合模式的抽象構建,Category相當於 容器構建,News相當於葉子構建。這樣我們就可以畫出新聞系統核心UML圖:

image 

新聞系統代碼:

public abstract class NewsComponent
{
    protected string title;
    public NewsComponent(string title)
    {
        this.title = title;
    }
    public abstract void Add(NewsComponent newsComponent);
    public abstract void Remove(NewsComponent newsComponent);
    public abstract void Display(int depath);
}

public class Category : NewsComponent
{
    private IList<NewsComponent> categories = new List<NewsComponent>();
    public Category(string title):base(title)
    {
    }
    public override void Add(NewsComponent newsComponent)
    {
        categories.Add(newsComponent);
    }

    public override void Remove(NewsComponent newsComponent)
    {
        categories.Add(newsComponent);
    }

    public override void Display(int depath)
    {
        Console.WriteLine(new String('-', depath) + title);
        foreach (var category in categories)
        {
            category.Display(depath+2);
        }
    }
}
public  class News: NewsComponent
{
    private string content;
    public News(string title, string content):base(title)
    {
        this.content = content;
    }
    public override void Add(NewsComponent newsComponent)
    {
        Console.WriteLine("Cannot add to a News");
    }

    public override void Remove(NewsComponent newsComponent)
    {
        Console.WriteLine("Cannot remove from a News");
    }

    public override void Display(int depath)
    {
        Console.WriteLine(new String('-', depath) + this.title + "[content]:" + this.content);
    }
}

客戶端調用:

static void Main(string[] args)
{
    NewsComponent newsComponent = new Category("新聞");
    newsComponent.Add(new Category("政治新聞"));
    newsComponent.Add(new News("【政治頭條】-美國大選 特朗普生出將於將在北京時間明日凌晨3:45宣誓就職美國總統", "..."));
    newsComponent.Add(new Category("政治風雲"));

    var a = new Category("娛樂新聞");
    a.Add(new News("【娛樂頭條】-由王安全執導的<<監獄風雲>>將於今天0點在各大影院全面上映", "...."));
    a.Add(new Category("娛樂八卦"));
    newsComponent.Add(a);

    var b = new Category("財經新聞");
    b.Add(new News("【財經頭條】-由於受土耳其貨幣危機的影響,美國及歐洲股市全線跌幅超1%", "..."));
            
    var c = new Category("股市風雲");
    b.Add(c);

    c.Add(new Category("歐洲股市"));
    c.Add(new Category("美股風雲"));
    c.Add(new Category("日韓股市"));
    newsComponent.Add(b);

    var d = new Category("體育新聞");
    var e = new Category("籃球");
    d.Add(e);
    var f=new Category("NBA");
    var g= new Category("CBA");
    e.Add(g);
    e.Add(f);
            
    newsComponent.Add(d);           
 
    newsComponent.Display(1);
    Console.ReadKey();
}

輸出結果:

image

五、透明組合模式與安全組合模式

在上面的實例中我們發現,Component構件類中出現了Add,Remove方法,但是在葉子節點類(Leaf)中不得不去實現,但是葉子節點是不需要這些方法的,看起來有些雞肋。雖然客戶端實現了無差別調用,雖然可以針對抽象編程,但是一旦調用到了葉子節點的這些方法,軟體可能會出現異常或者無意義的調用。那麼我們有什麼方法來改變呢?

可以將這些方法從Component中移到Composite 中,這樣就可以避免這種情況。只在Component中定義Composite和Leaf公用的方法。

那麼組合模式的結構圖就變成這樣:
image

這時圖中的各個角色沒有變化,僅僅是在Component去掉了一些抽象方法,在調用的時就不能用抽象構建Component來聲明瞭,要用具體的Composite來聲明,這樣在客戶端調用時就需要區別對待Composite和Leaf了,不能針對抽象Component構件編程了。

1、Component2(抽象構件):

它可以是介面或抽象類,為葉子構件和容器構件對象聲明介面,在該角色中可以包含所有子類共有行為的聲明和實現。在抽象構件中定義了訪問及管理它的子構件的方法,如增加子構件、刪除子構件、獲取子構件等。

2 、Composite2(容器構件):

它在組合結構中表示容器節點對象,容器節點包含子節點,其子節點可以是葉子節點,也可以是容器節點,它提供一個集合用於存儲子節點,實現了在抽象構件中定義的行為,包括那些訪問及管理子構件的方法,在其業務方法中可以遞歸調用其子節點的業務方法。

3、Leaf2(葉子構件):

它在組合結構中表示葉子節點對象,葉子節點沒有子節點,它實現了在抽象構件中定義的行為。對於那些訪問及管理子構件的方法,可以通過異常等方式進行處理。

在組合模式中根據抽象構建定義的形式, 可以將組合模式更稱 安全組合模式和透明組合模式。

安全組合模式:

安全組合模式只在抽象構件類中定義葉子節點(Leaf)和容器節點(Composite)都共有的方法,將容器方法移至容器節點中, 它的一般結構如下圖:

image 安全組合模式UML圖

安全模式的好處是,在客戶端調用葉子節點時不會出現調用不安全的方法,因為葉子節點沒有該方法,抽象構件中也沒有該方法。不好的地方是,葉子節點和容器節點的實現出現了差別,不能在客戶端統一使用抽象構件(Component)來編程了,必須要區別對待葉子節點(Leaf)和容器節點(Component)。

透明組合模式

透明組合模式是將對容器構件的管理方法都定義在抽象構件(Component)中,在葉子節點(Leaf)和容器節點(Composite)都進行實現,在葉子節點中針對相關的管理方法進行相關異常處理,或者友好提示的處理實現,  一般情況下透明組合模式的UML如下:

 image

透明模式UML圖

透明模式的好處是可以給客戶端調用時提供統一,無差別的對待葉子節點(Leaf)和容器節點(Composite),並且可以針對抽象構件(Component)進行編程。透明模式的缺點是如果調用到葉子節點(Leaf) 上的相關方法會導致程式異常。

六、組合模式的優點

  1. 組合模式可以清楚地定義分層次的複雜對象,表示對象的全部或部分層次,它讓客戶端忽略了層次的差異,方便對整個層次結構進行控制。
  2. 客戶端可以一致地使用一個組合結構或其中單個對象,不必關心處理的是單個對象還是整個組合結構,簡化了客戶端代碼。
  3. 在組合模式中增加新的容器構件和葉子構件都很方便,無須對現有類庫進行任何修改,符合“開閉原則”。
  4. 組合模式為樹形結構的面向對象實現提供了一種靈活的解決方案,通過葉子對象和容器對象的遞歸組合,可以形成複雜的樹形結構,但對樹形結構的控制卻非常簡單。

七、組合模式的缺點

組合模式,控制容器節點的類型不太容易。

八、組合模式的使用場景

  1. 在具有整體和部分的層次結構中,希望通過一種方式忽略整體與部分的差異,客戶端可以一致地對待它們。
  2. 在一個使用面向對象語言開發的系統中需要處理一個樹形結構。
  3. 在一個系統中能夠分離出葉子對象和容器對象,而且它們的類型不固定,需要增加一些新的類型。

Composite模式在平常的開發過程中使用的非常多,因為他提供了一種面向對象的操作樹形結構的方法,樹形結構在開發中頻繁出現。


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

-Advertisement-
Play Games
更多相關文章
  • Day3 Form表單 一.form表單 :提交數據 表單在網頁中主要負責數據採集功能,它用<form>標簽定義。 用戶輸入的信息都要包含在form標簽中,點擊提交後,<form>和</form>裡面包含的數據將被提交到伺服器或者電子郵件里。 所有的用戶輸入內容的地方都用表單來寫,如登錄註冊、搜索框 ...
  • 協議可以實現前後端全雙工通信,從而取代浪費資源的長輪詢。在此協議的基礎上,可以實現前後端數據、多端數據,真正的 實時響應 。在學習 的過程中,實現了一個簡化版群聊,過程和代碼詳細記錄在這篇文章中。 本篇文章來自 "董沅鑫的個人網站" ,引用、轉載請指明出處 。 查看更多知識,或者技術交流:請訪問 " ...
  • react教程(零)—— 安裝 react項目的基本配置,使用create-react-app和webpack構建react-app。主要介紹如何新建一個react-app,並對webpack如何創建react-app做了詳細介紹。 ...
  • Vue.js 組件 模塊化:是從代碼邏輯的角度進行劃分的; 組件化:是從UI界面的角度進行劃分的。 組件(Component)是 Vue.js 最強大的功能之一,組件可以擴展 HTML 元素,封裝可重用的代碼。 組件系統讓我們可以用獨立可復用的小組件來構建大型應用,幾乎任意類型的應用的界面都可以抽象 ...
  • Modal組件 長話不多說,接下來讓我們來動手實現一個react Modal組件。 我們先來看一下 "實際效果" Modal的佈局 首先,讓我們先思考下一個Modal組件的佈局是怎麼樣的。 我們先拿一個基本的Modal樣例來分析下。 如上圖所示,一個Modal組件可以分為mask、header、bo ...
  • Day1 瞭解web前端 一.職業發展路線: 前端頁面製作、前端開發、前端架構師 二.1)前端工程師主要職責: 利用HTML/CSS/JavaScript等各種Web技術進行客戶端產品的開發。完成客戶端程式(也就是瀏覽器端)的開發,同時結合後臺技術模擬整體效果,致力於通過技術改善用戶體驗。 2)和前 ...
  • puppeteer puppeteer 是一個通過DevTools 協議提供高級API 來控制 chrome,chromium 的 NODE庫; puppeteer預設運行在 headless 模式, 也可配置後運行在全模式(non headless). puppeteer可以做什麼 大部分在瀏覽器 ...
  • 寫在前面 註:這個系列是本人對js知識的一些梳理,其中不少內容來自書籍:Javascript高級程式設計第三版和JavaScript權威指南第六版,感謝它們的作者和譯者。有發現什麼問題的,歡迎留言指出。 1.數據屬性 數據屬性的4個特性: Configurable:①表示能否通過delete刪除屬性 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...