1. 程式人生 > >[2.3]、組合模式(C#)

[2.3]、組合模式(C#)

一、引言

在軟體開發過程中,我們經常會遇到處理簡單物件和複合物件的情況,例如對作業系統中目錄的處理就是這樣的一個例子,因為目錄可以包括單獨的檔案,也可以包括資料夾,資料夾又是由檔案組成的,由於簡單物件和複合物件在功能上區別,導致在操作過程中必須區分簡單物件和複合物件,這樣就會導致客戶呼叫帶來不必要的麻煩,然而作為客戶,它們希望能夠始終一致地對待簡單物件和複合物件。然而組合模式就是解決這樣的問題。

二、組合模式的詳細介紹

2.1 組合模式的定義

組合模式允許你將物件組合成樹形結構來表現”部分-整體“的層次結構,使得客戶以一致的方式處理單個物件以及物件的組合。下面我們用繪製的例子來詳細介紹組合模式,圖形可以由一些基本圖形元素組成(如直線,圓等),也可以由一些複雜圖形組成(由基本圖形元素組合而成),為了使客戶對基本圖形和複雜圖形的呼叫保持一致,我們使用組合模式來達到整個目的。

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

2.2 組合模式的實現

介紹完組合模式的定義之後,讓我們以圖形的例子來實現組合模式,具體程式碼如下:

// 通過一些簡單圖形以及一些複雜圖形構建圖形樹來演示組合模式
    // 客戶端呼叫
    class Client
    {
        static void Main(string[] args)
        {
            ComplexGraphics complexGraphics = new ComplexGraphics("一個複雜圖形和兩條線段組成的複雜圖形");
            complexGraphics.Add(new Line("線段A"));
            ComplexGraphics CompositeCG = new ComplexGraphics("一個圓和一條線組成的複雜圖形");
            CompositeCG.Add(new Circle("圓"));
            CompositeCG.Add(new Circle("線段B"));
            complexGraphics.Add(CompositeCG);
            Line l = new Line("線段C");
            complexGraphics.Add(l);
            // 顯示覆雜圖形的畫法
            Console.WriteLine("複雜圖形的繪製如下:");
            Console.WriteLine("---------------------");
            complexGraphics.Draw();
            Console.WriteLine("複雜圖形繪製完成");
            Console.WriteLine("---------------------");
            Console.WriteLine();
            // 移除一個元件再顯示覆雜圖形的畫法
            complexGraphics.Remove(l);
            Console.WriteLine("移除線段C後,複雜圖形的繪製如下:");
            Console.WriteLine("---------------------");
            complexGraphics.Draw();
            Console.WriteLine("複雜圖形繪製完成");
            Console.WriteLine("---------------------");
            Console.Read();
        }
    }
    /// <summary>
    /// 圖形抽象類,
    /// </summary>
    public abstract class Graphics
    {
        public string Name { get; set; }
        public Graphics(string name)
        {
            this.Name = name;
        }
        public abstract void Draw();
        public abstract void Add(Graphics g);
        public abstract void Remove(Graphics g);
    }
    /// <summary>
    /// 簡單圖形類——線
    /// </summary>
    public class Line : Graphics
    {
        public Line(string name)
            : base(name)
        { }
        // 重寫父類抽象方法
        public override void Draw()
        {
            Console.WriteLine("畫  " + Name);
        }
        // 因為簡單圖形在新增或移除其他圖形,所以簡單圖形Add或Remove方法沒有任何意義
        // 如果客戶端呼叫了簡單圖形的Add或Remove方法將會在執行時丟擲異常
        // 我們可以在客戶端捕獲該類移除並處理
        public override void Add(Graphics g)
        {
            throw new Exception("不能向簡單圖形Line新增其他圖形");
        }
        public override void Remove(Graphics g)
        {
            throw new Exception("不能向簡單圖形Line移除其他圖形");
        }
    }
    /// <summary>
    /// 簡單圖形類——圓
    /// </summary>
    public class Circle : Graphics
    {
        public Circle(string name)
            : base(name)
        { }
        // 重寫父類抽象方法
        public override void Draw()
        {
            Console.WriteLine("畫  " + Name);
        }
        public override void Add(Graphics g)
        {
            throw new Exception("不能向簡單圖形Circle新增其他圖形");
        }
        public override void Remove(Graphics g)
        {
            throw new Exception("不能向簡單圖形Circle移除其他圖形");
        }
    }
    /// <summary>
    /// 複雜圖形,由一些簡單圖形組成,這裡假設該複雜圖形由一個圓兩條線組成的複雜圖形
    /// </summary>
    public class ComplexGraphics : Graphics
    {
        private List<Graphics> complexGraphicsList = new List<Graphics>();
        public ComplexGraphics(string name)
            : base(name)
        { }
        /// <summary>
        /// 複雜圖形的畫法
        /// </summary>
        public override void Draw()
        {        
            foreach (Graphics g in complexGraphicsList)
            {
                g.Draw();
            }
        }
        public override void Add(Graphics g)
        {
            complexGraphicsList.Add(g);
        }
        public override void Remove(Graphics g)
        {
            complexGraphicsList.Remove(g);
        }
    }

由於基本圖形物件不存在Add和Remove方法,上面實現中直接通過丟擲一個異常的方式來解決這樣的問題的,但是我們想以一種更安全的方式來解決——因為基本圖形根本不存在這樣的方法,我們是不是可以移除這些方法呢?為了移除這些方法,我們就不得不修改Graphics介面,我們把管理子物件的方法宣告放在複合圖形物件裡面,這樣簡單物件Line、Circle使用這些方法時在編譯時就會出錯,這樣的一種實現方式我們稱為安全式的組合模式,然而上面的實現方式稱為透明式的組合模式,下面讓我們看看安全式的組合模式又是怎樣實現的,具體實現程式碼如下:
/// 安全式的組合模式
    /// 此方式實現的組合模式把管理子物件的方法宣告在樹枝構件ComplexGraphics類中
    /// 這樣如果葉子節點Line、Circle使用了Add或Remove方法時,就能在編譯期間出現錯誤
    /// 但這種方式雖然解決了透明式組合模式的問題,但是它使得葉子節點和樹枝構件具有不一樣的介面。
    /// 所以這兩種方式實現的組合模式各有優缺點,具體使用哪個,可以根據問題的實際情況而定
    class Client
    {
        static void Main(string[] args)
        {
            ComplexGraphics complexGraphics = new ComplexGraphics("一個複雜圖形和兩條線段組成的複雜圖形");
            complexGraphics.Add(new Line("線段A"));
            ComplexGraphics CompositeCG = new ComplexGraphics("一個圓和一條線組成的複雜圖形");
            CompositeCG.Add(new Circle("圓"));
            CompositeCG.Add(new Circle("線段B"));
            complexGraphics.Add(CompositeCG);
            Line l = new Line("線段C");
            complexGraphics.Add(l);
            // 顯示覆雜圖形的畫法
            Console.WriteLine("複雜圖形的繪製如下:");
            Console.WriteLine("---------------------");
            complexGraphics.Draw();
            Console.WriteLine("複雜圖形繪製完成");
            Console.WriteLine("---------------------");
            Console.WriteLine();
            // 移除一個元件再顯示覆雜圖形的畫法
            complexGraphics.Remove(l);
            Console.WriteLine("移除線段C後,複雜圖形的繪製如下:");
            Console.WriteLine("---------------------");
            complexGraphics.Draw();
            Console.WriteLine("複雜圖形繪製完成");
            Console.WriteLine("---------------------");
            Console.Read();
        }
    }
    /// <summary>
    /// 圖形抽象類,
    /// </summary>
    public abstract class Graphics
    {
        public string Name { get; set; }
        public Graphics(string name)
        {
            this.Name = name;
        }
        public abstract void Draw();
        // 移除了Add和Remove方法
        // 把管理子物件的方法放到了ComplexGraphics類中進行管理
        // 因為這些方法只在複雜圖形中才有意義
    }
    /// <summary>
    /// 簡單圖形類——線
    /// </summary>
    public class Line : Graphics
    {
        public Line(string name)
            : base(name)
        { }
        // 重寫父類抽象方法
        public override void Draw()
        {
            Console.WriteLine("畫  " + Name);
        }
    }
    /// <summary>
    /// 簡單圖形類——圓
    /// </summary>
    public class Circle : Graphics
    {
        public Circle(string name)
            : base(name)
        { }
        // 重寫父類抽象方法
        public override void Draw()
        {
            Console.WriteLine("畫  " + Name);
        }
    }
    /// <summary>
    /// 複雜圖形,由一些簡單圖形組成,這裡假設該複雜圖形由一個圓兩條線組成的複雜圖形
    /// </summary>
    public class ComplexGraphics : Graphics
    {
        private List<Graphics> complexGraphicsList = new List<Graphics>();
        public ComplexGraphics(string name)
            : base(name)
        { }
        /// <summary>
        /// 複雜圖形的畫法
        /// </summary>
        public override void Draw()
        {
            foreach (Graphics g in complexGraphicsList)
            {
                g.Draw();
            }
        }
        public void Add(Graphics g)
        {
            complexGraphicsList.Add(g);
        }
        public void Remove(Graphics g)
        {
            complexGraphicsList.Remove(g);
        }
    }

2.3 組合模式的類圖

看完了上面兩者方式的實現之後,讓我們具體看看組合模式的類圖來理清楚組合模式中類之間的關係。

透明式的組合模式類圖:

21132831-30f99887dec34fadb21282f0158f72b

安全式組合模式的類圖:

21132847-48c4cdf839c84cbeb4ea42d323587e2

組合模式中涉及到三個角色:

  • 抽象構件(Component)角色:這是一個抽象角色,上面實現中Graphics充當這個角色,它給參加組合的物件定義出了公共的介面及預設行為,可以用來管理所有的子物件(在透明式的組合模式是這樣的)。在安全式的組合模式裡,構件角色並不定義出管理子物件的方法,這一定義由樹枝結構物件給出。

  • 樹葉構件(Leaf)角色:樹葉物件時沒有下級子物件的物件,上面實現中Line和Circle充當這個角色,定義出參加組合的原始物件的行為

  • 樹枝構件(Composite)角色:代表參加組合的有下級子物件的物件,上面實現中ComplexGraphics充當這個角色,樹枝物件給出所有管理子物件的方法實現,如Add、Remove等。

三、組合模式的優缺點

優點:

  1. 組合模式使得客戶端程式碼可以一致地處理物件和物件容器,無需關係處理的單個物件,還是組合的物件容器。

  2. 將”客戶程式碼與複雜的物件容器結構“解耦。

  3. 可以更容易地往組合物件中加入新的構件。

缺點:使得設計更加複雜。客戶端需要花更多時間理清類之間的層次關係。(這個是幾乎所有設計模式所面臨的問題)。

注意的問題:

  1. 有時候系統需要遍歷一個樹枝結構的子構件很多次,這時候可以考慮把遍歷子構件的結構儲存在父構件裡面作為快取。

  2. 客戶端儘量不要直接呼叫樹葉類中的方法(在我上面實現就是這樣的,建立的是一個樹枝的具體物件,應該使用GraphicscomplexGraphics = new ComplexGraphics("一個複雜圖形和兩條線段組成的複雜圖形");),而是借用其父類(Graphics)的多型性完成呼叫,這樣可以增加程式碼的複用性。

四、組合模式的使用場景

在以下情況下應該考慮使用組合模式:

  1. 需要表示一個物件整體或部分的層次結構。

  2. 希望使用者忽略組合物件與單個物件的不同,使用者將統一地使用組合結構中的所有物件。

五、組合模式在.NET中的應用

組合模式在.NET 中最典型的應用就是應用與WinForms和Web的開發中,在.NET類庫中,都為這兩個平臺提供了很多現有的控制元件,然而System.Windows.Forms.dll中System.Windows.Forms.Control類就應用了組合模式,因為控制元件包括Label、TextBox等這樣的簡單控制元件,同時也包括GroupBox、DataGrid這樣複合的控制元件,每個控制元件都需要呼叫OnPaint方法來進行控制元件顯示,為了表示這種物件之間整體與部分的層次結構,微軟把Control類的實現應用了組合模式(確切地說應用了透明式的組合模式)。

六、總結

到這裡組合模式的介紹就結束了,組合模式解耦了客戶程式與複雜元素內部結構,從而使客戶程式可以向處理簡單元素一樣來處理複雜元素

相關推薦

[2.3]組合模式C#

一、引言 在軟體開發過程中,我們經常會遇到處理簡單物件和複合物件的情況,例如對作業系統中目錄的處理就是這樣的一個例子,因為目錄可以包括單獨的檔案,也可以包括資料夾,資料夾又是由檔案組成的,由於簡單物件和複合物件在功能上區別,導致在操作過程中必須區分簡單物件和複合物件,這

組合模式C++

二叉樹 != back 分享 樹形 spa class blog 分鐘   組合模式:將對象組合成樹形結構以來表示"整體--部分"的層次結構。組合模式使得用戶對單個對象和組合對象的使用具有一致性。   關於組合模式的定義有個關鍵詞"樹形",這個很關鍵,也很常見,如二叉樹什麽

LoRa關鍵引數 1擴頻因子SF 2編位元速率CR 3訊號頻寬BW 4LoRa訊號頻寬BW符號速率Rs和資料速率DR的關係

 LoRa學習:LoRa關鍵引數(擴頻因子,編位元速率,頻寬)的設定及解釋 1、擴頻因子(SF) 2、編位元速率(CR) 3、訊號頻寬(BW) 4、LoRa訊號頻寬BW、符號速率Rs和資料速率DR的關係 5、 LoRa訊號頻寬、擴頻

設計模式C#——組合模式

推薦閱讀:  我的CSDN  我的部落格園  QQ群:704621321       遊戲通常包含許多檢視。主檢視中顯示角

python教程系列二.2.3運算類魔法函式

運算相關魔術方法 比較運算相關魔術方法 1._ _lt_ _() 格式: def __lt__(self,other): return 資料 特徵: 觸發時機:進行小於判斷時自動觸發 引數:2個引數第一個是self,第二個判斷

設計模式---組合模式C++實現

/************************************************************************* 組合模式:是把單個物件和組合物件放在一起使用,把操作單個物件,和操作整個物件搞的沒有太大差別 在遍歷的時候,如果儲存的是葉子

組合模式Composite

rem for remove 安全 tco fin trac roo 問題 組合模式:將對象組合成樹形結構以表示‘部分-整體’的層次結構。組合模式使得用戶對單個對象和組合對象的使用具有一致性。 public abstract class Company {

設計模式學習“觀察者模式C#

original pan 學習筆記 pri 接口 program date contain 兩個 《深入淺出設計模式》學習筆記第二章 需求: 開發一套氣象監測應用,如圖: 氣象站,目前有三種裝置,溫度、濕度和氣壓感應裝置。 WeatherData對象追蹤氣象站的數據,並更

策略模式C++

type include name quic 構造 排序算法 插入 實現 pla   策略模式:把一系列算法封裝起來,使之可以相互替換。這樣就可以使算法獨立於客戶端變化。   如我們有很多排序算法,但是在不通的環境中,需要使用不同算法,那就可以定義一個抽象類,提供統一的接口

GOF23設計模式組合模式composite

ret blog 構建 樹狀結構 遍歷 合成 str 管理 rri 一、組合模式概述   將對象組合成樹狀結構以表示“部分和整體”層次結構,使得客戶可以統一的調用葉子對象和容器對象。   (1)組合模式的使用場景      把部分和整體的關系用樹形結構來表示,從而使客戶端

代理模式C++

pan 記錄 proxy eal quest 對象 保護 時間 resource    年過完了,開始上班了,時間過得太快了,感覺過了個假年........   代理模式:為其他對象提供一種代理,以控制這個對象的訪問。   角色分工:Subject:抽象角色,聲明對真實對象

迭代器與組合模式轉載

程式碼實在太多了,偷個懶轉載大佬的 https://www.cnblogs.com/lzhp/p/3427704.html 迭代器模式 提供了一種方法順序訪問一個聚合物件中的各個元素,而又不暴露內部的表示 把在元素之間遍歷的責任交給迭代器,而不是聚合物件 角色

FastSocketC/C++FastSocket.NETC#與SuperSocket(純C#) 開源庫的區別介紹使用方法

一、FastSocket與SuperSocket  區別 裡面包含了視訊教程。 我們到底選擇哪一款開源的Socket框架?https://blog.csdn.net/abennet/article/details/79399713 二、新浪的FastSocket介紹

struts2[2.3]引數獲得方式-4集合型別引數封裝list和map

1.學習路線 今天咱們來學struts2引數獲得方式,let`go!                   

struts2[2.3]引數獲得方式-1屬性驅動獲得引數

1.學習路線 今天咱們來學struts2引數獲得方式,let`go!                   

設計模式-組合模式Composite

    組合模式是構造型模式的一種。通過遞迴手段來構造樹形的物件結構,並可以通過一個物件來訪問整個物件樹   角色和職責: 就是資料夾那種形式,樹形   UML圖:   具體程式碼: import java.util.List

軟考設計模式——裝飾模式C++

裝飾模式就是在不改變原來介面的情況下,給類新增功能。 舉個例子,在遊戲裡玩一個英雄,可以拿裝備,拿的這些個裝備就相當於是在裝飾自己。 其實有一個訣竅,就是這些類裡有一個方法名字都一樣,然後這些類先從外到內,然後從裡面開始一層層向外巢狀呼叫,相當於一直給這個方法里加東西

java設計模式之——建造者模式原型模式建立性【讀書筆記】

一、建造者模式(生成器模式)                 定義:將一個複雜物件的構建和它的表示分離開,使得同樣的構建過程可以得到不同的表示。                 效果:採用建造者模式,使用者只需要選擇建造的型別就可以得到它們,而具體的建造過程和細節就不需要

Decorator裝飾器模式C++

簡而言之,它提供了一種對被裝飾者透明的方法; 例如:一篇文章本身無需知道自己的頁首和頁尾;使用者可以很方便的新增不同的頁首與頁尾 對比Strategy模式:物件需要知道使用的是哪個演算法,該方式對元件不可見,但是呼叫者可以任意數量新增裝飾。 不足:每次裝飾都會引入一個新

設計模式之策略模式C++

設計模式之策略模式 策略模式定義演算法家族,分別封裝。它們之間可以相互替換,讓演算法變化,不會影響到使用者。優點:適合類中的成員方法為主,演算法經常變動;簡單了單元測試(因為每個演算法都有自己的類,可以通過自己的介面單獨測試)。缺點:客戶端需要做出判斷。 其UM