在我們開發過程中,我們可能需要用到一種設計,一個容器可以包含一個對象,並且也可以包含一個容器,我們需要通過一個接口或一個抽象類來表示這個容器或對象,也就是說容器或對象是同一總類型,但是包含關系,也就是說我們需要用一種模式來實現多叉樹的結構。節點可以包含字節點,也可以包含子項。
我們可以通過Composite模式來實現。
首先看看Composite模式的動機:
如何將"客戶代碼與復雜的對象容器結構"解耦?讓對象容器自己來實現自身的復雜結構,從而使得客戶代碼就像處理簡單對象一樣來處理復雜的對象容器?
Composite模式的意圖:
將對象組合成樹形結構以表示"部分-整體"的層次結構。Composite使得用戶對單個對象和組合對象的使用具有一致性。
看看實現的代碼
public interface IBox
...{
void Process();
void Add(IBox box);
void Remove(IBox box);
}
這是一個接口,它定義了對象和容器應該需要實現的方法,在這個接口裡Add和Remove方法是對象不需要實現的,可以在實現這兩個方法是拋出異常,或者為空方法。
public class SingleBox : IBox
...{
public void Process
...{
}
public void Add(IBox box)
...{
throw new Exception();
}
public void Remove(IBox box)
...{
throw new Exception();
}
}
這是對象類的定義,它僅僅實現過了Process方法,表明這個對象需要處理的工作。
public class ContainerBox : IBox
...{
ArrayList list = null;
public ContainerBox()
...{
list = new ArrayList();
}
public void Add(IBox box)
...{
list.Add(box);
}
public void Remove(IBox box)
...{
list.Remove(list);
}
public void Process()
...{
//1.Do Something for myself
//2.Do process for the box in the list
foreach (IBox box in list)
...{
box.Process();
}
}
}
這是容器的實現代碼在Add和Remove方法中,我們可以添加實現了IBox接口的對象或容器,在Process方法中,我們依次調用IBox接口的Process方法將每個Box處理一次。
那麼我們在客戶程序可以像如下代碼一樣使用
static void Main(string[] args)
...{
IBox box = Factory.GetBox();
box.Add(new SingleBox());
IBox box1 = Factory.GetBox();
box1.Add(box);
box1.Process();
}
可以說我們在容器的Process方法裡實現了遞歸。
Composite模式的幾個要點:
- Composite模式采用樹形結構來實現普遍存在的對象容器,從而將"一對多"的關系轉化為"一對一"的關系,使得客戶代碼可以一致地處理對象和對象容器,無需關心處理的是單個的對象,還是組合的對象容器。
- 將"客戶代碼與復雜的對象容器結構"解耦是Composite模式的核心思想,解耦之後,客戶代碼將與純粹的抽象接口--而非對象容器的復內部實現結構--發生依賴關系,從而更能"應對變化"。
- Composite模式中,是將"Add和Remove等和對象容器相關的方法"定義在"表示抽象對象的Component類"中,還是將其定義在"表示對象容器的Composite類"中,是一個關乎"透明性"和"安全性"的兩難問題,需要仔細權衡。這裡有可能違背面向對象的"單一職責原則",但是對於這種特殊結構,這又是必須付出的代價。ASP.Net控件的實現在這
方面為我們提供了一個很好的示范。
- Composite模式在具體實現中,可以讓父對象中的子對象反向追溯;如果父對象有頻繁的遍歷需求,可使用緩存技巧來改善效率。
Composite模式普遍用於控件中,如控件可以是容器,但是控件也可以被另外一個控件加入,那麼另外一個控件也是一個容器。在ASP.Net中也是如此。如果我們將IBox接口看作Control接口,Process方法改為Render方法,就是繪制自己。那麼我們會發現,我們把控件慢慢的組合成樹,然後使用樹的根節點的Render方法,那麼我們就將控件容器下的控件和控件容器都Render出來了。
在Asp.Net中Control是使用ControlCollection來實現的,那麼它是通過在IBox接口裡添加一個Box屬性,返回值為IBox,那麼我們對象實現這個接口的時候,get訪問器可以返回為空。這就是ASP.Net控件給我們提供的很好的示范。如何來權衡透明性和安全性。