組合模式(Composite)
1.1.1 摘要
在軟件系統(tǒng)設(shè)計(jì)中,我們經(jīng)常會(huì)遇到整體和部分的設(shè)計(jì)問(wèn)題,例如為一家架構(gòu)完善的公司設(shè)計(jì)系統(tǒng)時(shí),我們?cè)谙到y(tǒng)設(shè)計(jì)過(guò)程中應(yīng)該更加注重這種整體和部分的關(guān)系(總部和分部的關(guān)系),這就是我們今天要介紹的組合模式(Composite)。
組合模式(Composite)把對(duì)象構(gòu)造成為一棵對(duì)象樹,現(xiàn)在讓我們簡(jiǎn)短回憶一下樹的定義。
樹是一種數(shù)據(jù)結(jié)構(gòu),它是由n(n>=1)個(gè)有限結(jié)點(diǎn)組成一個(gè)具有層次關(guān)系的集合。把它叫做“樹”是因?yàn)樗雌饋?lái)像一棵倒掛的樹,也就是說(shuō)它是根朝上,而葉朝下的。它具有以下的特點(diǎn):
每個(gè)結(jié)點(diǎn)有零個(gè)或多個(gè)子結(jié)點(diǎn);
每一個(gè)子結(jié)點(diǎn)只有一個(gè)父結(jié)點(diǎn);
沒(méi)有前驅(qū)的結(jié)點(diǎn)為根結(jié)點(diǎn);
除了根結(jié)點(diǎn)外,每個(gè)子結(jié)點(diǎn)可以分為m個(gè)不相交的子樹(參考維基百科);
- 定義
組合模式(Composite),將對(duì)象組合成樹形結(jié)構(gòu)以表示‘部分-整體’的層次關(guān)系(樹狀結(jié)構(gòu))。組合模式使得用戶對(duì)單個(gè)對(duì)象和組合對(duì)象使用具有一致性。
- 意圖
希望用戶可以忽略單個(gè)對(duì)象和組合對(duì)象的區(qū)別,統(tǒng)一使用組合結(jié)構(gòu)中的所有對(duì)象(封裝變化的思想)。
- 結(jié)構(gòu)圖
圖1組合模式(Composite)結(jié)構(gòu)圖
- 參與者
Component
它是一個(gè)抽象角色,為對(duì)象的組合提供統(tǒng)一的接口。
為其派生類提供默認(rèn)的行為。
通過(guò)該接口來(lái)訪問(wèn)和管理Child Components(節(jié)點(diǎn)后繼)。
通過(guò)該接口來(lái)訪問(wèn)Parent Components(節(jié)點(diǎn)前驅(qū))。
Leaf
代表參加組合的葉子對(duì)象(葉子沒(méi)有后繼)。
定義組成原始對(duì)象的行為。
Composite
定義非葉子對(duì)象的行為。
實(shí)現(xiàn)Component中的孩子操作行為(如:Add(),Remove() 等)。
1.1.2 正文
在我們學(xué)習(xí)組合模式(Composite)之前,讓我們先講解一下透明方式和安全方式。
透明方式:在Component中聲明所有用來(lái)管理子對(duì)象的方法,如Add()方法,Remove()方法及GetChild()方法,所有實(shí)現(xiàn)Component接口的子類都具備這些方法,這使得Component和子類具備一致的行為接口,使得對(duì)客戶端無(wú)需區(qū)別樹葉和樹枝對(duì)象。
大家可以回憶一下代理模式(Proxy)中,Proxy,RealSubject類和Subject接口具備一致的行為接口,從而使得被代理者對(duì)于客戶端是透明的。
正由于我們的Composite和Leaf都具備一致的接口行為,但我們知道Leaf不應(yīng)該具有Add(),Remove()及GetChild()方法,因?yàn)槲覀內(nèi)~子節(jié)點(diǎn)不能再添加和移除節(jié)點(diǎn)了。
安全模式:在透明模式基礎(chǔ)上把Component中聲明所有用來(lái)管理子對(duì)象的方法移到Composite中,在Composite實(shí)現(xiàn)子對(duì)象的管理方法,那么Leaf就沒(méi)有子對(duì)象管理方法,這使得Composite和Leaf的行為接口不一致,所以客戶端在調(diào)用時(shí)要知道樹葉和樹枝對(duì)象存在。
圖2透明方式的組合模式(Composite)結(jié)構(gòu)圖
圖3安全方式的組合模式(Composite)結(jié)構(gòu)圖
通過(guò)前面的介紹我們知道透明和安全方式組合模式(Composite)的區(qū)別在于是否在抽象接口Component中定義子對(duì)象的管理行為。現(xiàn)在讓我們完成透明方式的組合模式(Composite)。
以下示例代碼演示透明方式的組合模式代碼:
首先我們定義一個(gè)抽象類Componet,在其中聲明Add(),Remove()和Display()等子對(duì)象操作發(fā)法及初始化字段name的公共行為。
/// <summary> /// The Component acts as abstract class. /// </summary> public abstract class Component { protected string name; public Component() { } public Component(string name) { this.name = name; } #region Methods public abstract Component Add(Component obj); public abstract void Remove(Component obj); public abstract void Display(int depth); #endregion }
接著我們定義Composite類繼承于Component,增加_children保存Component對(duì)象的引用,從而建立起由Component到Composite的聚集關(guān)系(Has-a關(guān)系),并且實(shí)現(xiàn)Component抽象類中的抽象方法。
/// <summary> /// The Composite acts as branch. /// </summary> public class Composite : Component { private IList<Component> _childen = new List<Component>(); public Composite() { } public Composite(string name) : base(name) { } /// <summary> /// Adds the branch or leaf obj. /// </summary> /// <param name="obj">The obj.</param> /// <returns></returns> public override Component Add(Component obj) { this._childen.Add(obj); return obj; } /// <summary> /// Removes the branch or leaf obj. /// </summary> /// <param name="obj">The obj.</param> public override void Remove(Component obj) { this._childen.Remove(obj); } /// <summary> /// Displays the tree's depth. /// </summary> /// <param name="depth">The depth.</param> public override void Display(int depth) { StringBuilder depthBuilder = new StringBuilder(new String('-', depth) + name); Console.WriteLine(depthBuilder); foreach (Component child in _childen) { child.Display(depth + 1); } } }
最后我們定義Leaf類它也是繼承于Component,在其中我們實(shí)現(xiàn)Component定義的行為接口,這使得Composite和Leaf類具有統(tǒng)一的接口行為,但我們并沒(méi)有在Leaf的行為方法中給出具體的實(shí)現(xiàn),因?yàn)槿~子節(jié)點(diǎn)并沒(méi)有后繼,所以沒(méi)有必要實(shí)現(xiàn)Add()和Remove()等行為。
/// <summary> /// The "Leaf" acts as Leaf. /// </summary> public class Leaf : Component { public Leaf(string name) : base(name) { } public Leaf() { } /// <summary> /// Adds the branch or leaf obj. /// </summary> /// <param name="obj">The obj.</param> /// <returns></returns> public override Component Add(Component obj) { throw new NotSupportedException(); } /// <summary> /// Removes the branch or leaf obj. /// </summary> /// <param name="obj">The obj.</param> public override void Remove(Component obj) { throw new NotSupportedException(); } /// <summary> /// Displays the tree's depth. /// </summary> /// <param name="depth">The depth.</param> public override void Display(int depth) { StringBuilder depthBuilder = new StringBuilder(new String('-', depth) + name); Console.WriteLine(depthBuilder); } }
我們定義一個(gè)工廠方法來(lái)創(chuàng)建Composite和Leaf對(duì)象,大家可以注意到在創(chuàng)建一個(gè)Component對(duì)象之后,我們調(diào)用子對(duì)象管理方法時(shí),并不需要判斷當(dāng)前對(duì)象是由Composite類,還是有Leaf類創(chuàng)建得來(lái),這就是透明方式的組合模式(Composite)。
public class Program { static void Main(string[] args) { Client client = new Client(); Component component = client.Get("root"); Component componentA = component.Add(new Composite("BranchA")); componentA.Add(new Leaf("LeafA")); componentA.Add(new Leaf("LeafB")); Component componentB = component.Add(new Composite("BranchB")); component.Display(1); Console.ReadKey(); } }
圖4輸出結(jié)果
前面例子給出了透明方式組合模式(Composite)的實(shí)現(xiàn),我們?cè)贑omponent中聲明了Add() 和Remove()操作子對(duì)象方法,使得Composite和Leaf類具備一致的行為接口,客戶端可以正常調(diào)用Composite中的操作子對(duì)象方法,但如果客戶端嘗試調(diào)用Leaf中操作子對(duì)象方法時(shí)會(huì)拋出異常,因?yàn)槿~子節(jié)點(diǎn)沒(méi)有后繼節(jié)點(diǎn)了,假設(shè)我們要實(shí)現(xiàn)安全方式組合模式(Composite)客戶端在調(diào)用操作子對(duì)象方法時(shí),需要判斷當(dāng)前對(duì)象是葉子節(jié)點(diǎn)或枝節(jié)點(diǎn)。
Component component = client.Get("root"); if (component is Composite) { //// Implemention code. }
接下來(lái)讓我們通過(guò)繪圖程序?qū)嵗榻B安全方式的組合模式(Composite)的實(shí)現(xiàn),首先我們定義一個(gè)抽象類DrawingProgramm,其角色為Component,然后再添加三個(gè)子類Shape,Triangle和Circle,它們的角色分別為:Composite,Leaf和Leaf。
現(xiàn)在把子對(duì)象的管理方法都移到Shape類中,所以對(duì)客戶端來(lái)說(shuō)調(diào)用子對(duì)象時(shí),它了解Shape、Triangle和Circle直接的區(qū)別。
以下示例代碼演示安全方式的組合模式代碼:
首先我們定義一個(gè)抽象類DrawingProgramming(即Componet),在其中聲明Draw()及初始化字段_name的公共行為。
/// <summary> /// The "DrawingProgramming" class acts as component. /// </summary> public abstract class DrawingProgramming { protected string _name; public abstract void Draw(); public DrawingProgramming(string name) { this._name = name; } }
接著我們定義Shape類繼承于DrawingProgramming,增加_children保存DrawingProgramming對(duì)象的引用,也是建立起由DrawingProgramming到Shape的聚集關(guān)系(Has-a關(guān)系),大家要注意到安全方式的組合模式(Composite)對(duì)子對(duì)象的管理方法都移到Shape類(即Composite)中,使得Leaf和Composite的行為接口不一致。
/// <summary> /// The "Shape" class acts as composite. /// </summary> public class Shape : DrawingProgramming { IList<DrawingProgramming> _children = new List<DrawingProgramming>(); public Shape(string name) : base(name) { } #region DrawingProgramming 成員 public override void Draw() { StringBuilder depthBuilder = new StringBuilder(_name); Console.WriteLine(depthBuilder); foreach (DrawingProgramming child in _children) { child.Draw(); } } #endregion #region Methods public void Add(DrawingProgramming dp) { _children.Add(dp); } public void Remove(DrawingProgramming dp) { _children.Remove(dp); } #endregion }
最后我們定義Triangle和Circle類它也是繼承于DrawingProgramming,在其中我們實(shí)現(xiàn)DrawingProgramming定義的Draw()行為,這使得Composite和Leaf類具有不一致的接口行為,這樣我們可以避免在Leaf中實(shí)現(xiàn)沒(méi)有必要地接口,從而使得我們?cè)O(shè)計(jì)符合SRP原則(對(duì)象職責(zé)單一原則)。
/// <summary> /// The "Triangle" class acts as leaf. /// </summary> public class Triangle : DrawingProgramming { public Triangle(string name) : base(name) { } #region DrawingProgramming 成員 public override void Draw() { StringBuilder depthBuilder = new StringBuilder(_name); Console.WriteLine(depthBuilder); } #endregion } /// <summary> /// The "Circle" class acts as leaf. /// </summary> public class Circle : DrawingProgramming { public Circle(string name) : base(name) { } #region DrawingProgramming 成員 public override void Draw() { StringBuilder depthBuilder = new StringBuilder(_name); Console.WriteLine(depthBuilder); } #endregion }
大家注意到在我們給出的例子中,我們都是把對(duì)象一個(gè)接一個(gè)的鏈接起來(lái),值就是像是一條對(duì)象鏈,不禁讓人想起裝飾者模式(Decorator)中的裝飾對(duì)象鏈。
組合模式(Composite)和裝飾者模式(Decorator)的關(guān)系
圖5 Decorator中的Composite
通過(guò)上圖我們可以發(fā)現(xiàn),裝飾者模式(Decorator)是通過(guò)組合模式(Composite)來(lái)構(gòu)建起一棵對(duì)象樹,然后通過(guò)裝飾者的子類來(lái)擴(kuò)展裝飾者(即Composite)的行為,從而實(shí)現(xiàn)對(duì)具體組件裝飾行為(即Leaf)。
1.1.3 總結(jié)
1.組合模式(Composite)采用樹形層次結(jié)構(gòu)來(lái)實(shí)現(xiàn)對(duì)象容器,如:繪圖程序例子我們把各種各樣的圖形添加的Shape中,從而構(gòu)造起一條對(duì)象鏈,把“一對(duì)多”的關(guān)系轉(zhuǎn)化“一對(duì)一”的層次關(guān)系,使得客戶代碼可以一致地處理對(duì)象和對(duì)象容器,無(wú)需關(guān)心處理的是單個(gè)的對(duì)象,還是組合的對(duì)象容器。
2.組合模式(Composite)中,透明方式和安全方式的使用抉擇,雖然透明方式有可能違背面向?qū)ο蟮腟RP原則(單一職責(zé)),而安全方式?jīng)]有很好的封裝變化,但在實(shí)際開發(fā)時(shí)我們要根據(jù)具體的情況權(quán)衡利弊。
|
|
關(guān)于作者:[作者]:
JK_Rush從事.NET開發(fā)和熱衷于開源高性能系統(tǒng)設(shè)計(jì),通過(guò)博文交流和分享經(jīng)驗(yàn),歡迎轉(zhuǎn)載,請(qǐng)保留原文地址,謝謝。 |

1.1.1 摘要 在軟件系統(tǒng)設(shè)計(jì)中,我們經(jīng)常會(huì)遇到整體和部分的設(shè)計(jì)問(wèn)題,例如為一家架構(gòu)完善的公司設(shè)計(jì)系統(tǒng)時(shí),我們?cè)谙到y(tǒng)設(shè)計(jì)過(guò)程中應(yīng)該更加注重這種整體和部分的關(guān)系(總部和分部的關(guān)系),這就是我們今天要介...





浙公網(wǎng)安備 33010602011771號(hào)