系統架構技能之設計模式—組合模式
一、上篇回顧
我們上篇主要講述了結構型模式中的外觀模式,外觀模式作為結構型模式中的一個簡單又實用的模式,外觀模式通過封裝細節來提供大粒度的調用,直接的好處就是,封裝細節,提供了應用寫程序的可維護性和易用性。外觀模式一般應用在系統架構的服務層中,當我們是多個不同類型的客戶端應用程序時,比如一個系統既可以在通過Web的形式訪問,也可以通過客戶端應用程序的形式時,可能通過外觀模式來提供遠程服務,讓應用程序進行遠程調用,這樣通過外觀形式提供服務,那么不管是什么樣的客戶端都訪問一致的外觀服務,那么以后就算是我們的應用服務發生變化,那么我們不需要修改沒一個客戶端應用的調用,只需要修改相應的外觀應用即可。
我們主要是講述了以下的幾種情況,使用外觀模式可能更適合:
1、我們在使用第三方類庫或者API的時候,我們通過本地的API接口的封裝,來完成對第三方API接口的粗粒度外觀對象,通過這個外觀對象可以很容易的完成服務的調用。
2、我們在架構設計的過程中,一次的功能訪問可能需要同時的調用很多個對象,那么如果我們在服務調用的時候,能夠在應用程序調用中一次就能完成所有要同時調用的對象那該多好啊,外觀模式無疑是最好的原則,特別是在分布式應用中,通過遠程調用服務,通過外觀模式降低應用程序與服務的交互次數,同時可以降低應用程序的復雜性。
二、摘要
本文將會講述結構性模式中的另外一個常用的模式-組合模式,我們平時在面向對象的設計中,我想有一個原則經常被提及就是,我們在設計的時候,對象組合>類的繼承,本篇將會將結合簡單的實例來說明這方面的優勢,并且完成對組合模式的主題思想的掌握。我們這樣來簡單的理解組合模式,組合模式就是把一些現有的對象或者元素,經過組合后組成新的對象,新的對象提供內部方法,可以讓我們很方便的完成這些元素或者內部對象的訪問和操作。我們也可以把組合對象理解成一個容器,容器提供各種訪問其內部對象或者元素的API,我們只需要使用這些方法就可以操作它了。那么對象組合相比繼承的優勢有哪些呢?可能具體的優勢,也不是一句二句就能表述清楚的,還是我們來看看圖形的可視化的描述吧。
我們這里設計的是持久化服務寫到一個基類中,然后繼承自該基類的子類都會擁有內置的持久化方法,可能后續我們又要添加其他的針對某個具體的對象類,有一些個性化的服務,我們通過擴展這個類,進行繼承,這樣多重繼承后,會有一個很大的問題。子類膨脹的問題,而且一般來說繼承的重數達到5層左右的時候,性能上可能就會有一定的瓶頸。也不是好的設計的思路。這時候對象組合可能為我們提供了更好的解決方案。基于組合方式的話,可能我們可以這樣來做,換個思路:就像我們的一個負責的對象,可以通過簡單的對象來組成的道理差不多,其實。
通過上圖,我們知道了,組合對象可以看作是一系列簡單的對象組合成負責的對象的一個模式,復雜對象可以看作是簡單對象的一個容器。這個復雜對象完成了簡單對象的封裝,通過這個容器完成對象內部簡單對象的訪問。
三、本文大綱
a、上篇回顧。
b、摘要。
c、本文大綱。
d、組合模式的特點及使用場景。
e、組合模式的經典實現。
f、組合模式的其他方案。
g、原型模式使用總結。
四、組合模式的特點及使用場景
組合模式是將一系列對象組合成樹形結構用來表示整體和部分之間的關系,組合模式的主要目的是達到,訪問組合對象和訪問單個對象具有一致性。這里的組合對象比較特殊,本身他可以是由其他的對象組合而成,同時,這個組合對象又可以是組成更復雜對象的一個部分。我們來舉個例子來說明吧,可能更直觀。
這里我們可以理解為一個簡單的查詢組件可能有要滿足上述的幾類查詢條件的輸入類型,將這個組件作為一個容器,那么同事,這個容器又可以作為另外一個容器的組件來運行,那么我們又可以在分頁控件中,將這個查詢組件包含到其中。以為分頁提供相應的查詢元素的集成。
我們一般在如下場景中使用組合模式比較方便:
1、我們有的時候想用戶使用一個復雜對象像使用簡單對象一樣的方式去訪問,并且用戶同意使用對象內部的所有的對象時,我們可以考慮使用該模式。這個怎么理解呢?我們使用復雜對象像使用簡單對象一樣的方式去訪問的話,那么我們可以使用組合對象,我們把這些簡單的對象進行組合,用組合對象進行包裝,并且提供相應的操作組合對象內部的方法。結合上圖中的例子,我們可以這樣理解,我們把查詢組件封裝成一個用戶控件,然后可以在其他的頁面中進行調用。這個時候,我們可能考慮如何分部頁面,如何能夠動態的維護界面上的控件,是否顯示,控件里面顯示的文本和值是什么?等等,這些都是我們可以考慮的元素,那么我們如何來做呢?提供下面的幾個方法。我還是給出圖來說話吧:
包含上面的這些元素,那么我們可以通過一些相應的方法來進行訪問內部的元素。我們給出簡單的示例代碼:
{
InitializeComponent();
this.InitControlList();
}
public event EventHandler handler;
private Dictionary<string,Control> _controlList = null;
/// <summary>
/// 初始化控件信息
/// </summary>
private void InitControlList()
{
this._controlList = new Dictionary<string,Control>();
}
/// <summary>
/// 返回界面控件中所有的查詢條件控件列表
/// </summary>
/// <returns></returns>
public Dictionary<string,Control> GetControls()
{
return this._controlList;
}
/// <summary>
/// 添加查詢控件到界面中
/// </summary>
/// <param name="control"></param>
/// <returns></returns>
public bool AddControl(string key,Control control)
{
if(this._controlList.ContainsKey(key))
return false;
this._controlList.Add(key,control);
return true;
}
/// <summary>
/// 移除指定鍵值的對象
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool RemoveControl(string key)
{
if (this._controlList.ContainsKey(key))
return false;
this._controlList.Remove(key);
return true;
}
public virtual void OnQuery()
{
if (this.handler != null)
handler(this, new EventArgs());
}
public void Query(object sender, EventArgs e)
{
this.CreateSQL();
this.OnQuery();
}
/// <summary>
/// 根據選中的條件生成SQL語句
/// </summary>
private void CreateSQL()
{
throw new NotImplementedException();
}
上面的代碼很簡單就是給出了基本的思路,并不是完整的功能。我們來看其他的可能會用到組合模式的情況。
2、如果有的時候,我們希望用戶不了解自己使用的對象有多復雜,并且組合對象可以的內部可以自由的變化和組合,但是不會影響到客戶應用程序使用這個組合對象,如果項目中需要新增一個組合對象的時候,客戶調用的程序還是一樣,不會因為這個組合對象發生變更而發生變化。組合模式在解決整體和部分之間的問題應用很廣泛,也可以降低系統的復雜度,因為我們可以把復雜的組件看作另一個組件的組成部分來處理。就像上面的查詢組件,那么我們可以把查詢組件作為分頁組件的一個部分來處理。我這里就不給出示例代碼了通過上面的情況,我們可以大概的知道,組合模式的使用場景。下面我們就要結合實例說明組合模式的用處了。
五、組合模式的經典實現
我們先來看看使用經典的組合模式來實現我們上面說的查詢控件吧,看看和其他的方案有什么樣的不同,不過大體的思路都是一致的,把其他的對象進行組合成復雜的對象,然后提供操作內部對象的方法,具體的方式可以很多。我們這里還是以上面的查詢組件和分頁組件為例來說明具體的實現思路,當然我這里只是給出核心代碼,但不是完整的代碼:
我們先給出控件內部的完整定義:
{
InitializeComponent();
this.InitControlList();
}
public event EventHandler handler;
private Dictionary<string,Control> _controlList = null;
/// <summary>
/// 初始化控件信息
/// </summary>
private void InitControlList()
{
this._controlList = new Dictionary<string,Control>();
}
/// <summary>
/// 返回界面控件中所有的查詢條件控件列表
/// </summary>
/// <returns></returns>
public Dictionary<string,Control> GetControls()
{
return this._controlList;
}
/// <summary>
/// 添加查詢控件到界面中
/// </summary>
/// <param name="control"></param>
/// <returns></returns>
public bool AddControl(string key,Control control)
{
if(this._controlList.ContainsKey(key))
return false;
this._controlList.Add(key,control);
return true;
}
/// <summary>
/// 移除指定鍵值的對象
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public bool RemoveControl(string key)
{
if (this._controlList.ContainsKey(key))
return false;
this._controlList.Remove(key);
return true;
}
public virtual void OnQuery()
{
if (this.handler != null)
handler(this, new EventArgs());
}
public void Query(object sender, EventArgs e)
{
this.CreateSQL();
this.OnQuery();
}
/// <summary>
/// 根據選中的條件生成SQL語句
/// </summary>
private void CreateSQL()
{
throw new NotImplementedException();
}
public virtual Control this[string key]
{
get
{
return this._controlList[key];
}
set
{
this._controlList[key] = value;
}
}
public virtual IEnumerable<Control> GetControlList()
{
if (this._controlList.Count > 0)
{
foreach (Control control in this._controlList.Values)
{
foreach (Control item in control.Controls)
{
yield return control;
}
}
}
}
這時候我們如果想要重寫上面的方案,可能有時候我們的查詢控件需要基于上面的幾個基本的操作方法進行重寫,完成自定義的組合對象的操作:
{
public QueryPanelText()
{
InitializeComponent();
}
public override Control this[string key]
{
get
{
return base.GetControls()[key];
}
set
{
base.GetControls()[key] = value;
}
}
/// <summary>
/// 獲取集合中的所有控件集合
/// </summary>
/// <returns></returns>
public override IEnumerable<Control> GetControlList()
{
return base.GetControlList();
}
}
當然我上面并沒有改變對象的任何行為,這里定義了公共的行為,有的時候我們需要限制一些對象內部對象的某些行為,這時候我們通過繼承公共的對象來重寫對象的行為來完成。例如從組合對象衍生出來的某些組合對象,可能不具有父類的某些行為時,我們可以通過重寫父類的行為來完成自定義的操作。
六、組合模式的其他方案
6.1、通過過濾器來改進上述方案
有的時候,我們可能并不關心對象中的所有的元素,我們只是操作其中的具有同類特征的對象,比如對于上面的查詢控件中的對象,我們可能想要獲取設置條件的控件項,或者說我們相應控制文本框或者下拉框的樣式或者高度等等,這個時候,我們需要對組合對象內的元素就行篩選。這個時候,我們可能可以采用過濾器來實現這樣的功能。
對于上面的結果,我們可能需要獲取界面中所有的文本框,或者下拉框,或者其他類型的控件等,這時候我們可以如下方式來做,通過接口定義,根據傳入的不同的過濾器,返回過濾后的結果集合。我們先定義一個過濾器接口:
{
bool IsMatch(Control control);
}
我們這里定義一個文本過濾器的實現。
{
#region ISelectRule 成員
public bool IsMatch(System.Windows.Forms.Control control)
{
if (control is System.Windows.Forms.TextBox)
return true;
return false;
}
#endregion
}
我們來看看具體的查詢控件內部的獲取內部元素的方法:
/// 獲取集合中的所有控件集合
/// </summary>
/// <returns></returns>
public IEnumerable<Control> GetControlList(ISelectRule rule)
{
foreach (Control control in base.GetControls().Values)
{
if (rule.IsMatch(control))
yield return control;
}
}
通過上面的幾行代碼的改進,我們就可以完成對內部的組合對象的改進,這樣就可以完成根據不同的查詢規則,來返回不同的內部對象集合。
通過上面的迭代器來封裝我們的內部組合對象,我們通過迭代器來完成組合對象的過濾。我們還可以通過XML文件來組織我們的對象組合的結構,XML文件先天性的優勢就是結構化的語言,特別適合這樣的整體與局部關系的結構,所以我們的組合對象也可以通過XML文件來組織,可以將復雜對象設置為一個復雜的節點,該節點下包含多個對象時,我們只需要將每個對象設置為一個節點,通過XPath語言完成查詢。
6.2XML文件格式的組合模式
使用XML文件來完成組合模式有如下優點:
1、我們有系統提供的類庫自動完成對XML文件的操作和訪問。
2、XML提供了基于XPath解析的查詢語言。
也有一些點,就是操作XML文件,調試起來會比較麻煩,而且進行代碼編寫和測試是個不太方便的事情。
我們來看看如果我們把上面的獲取迭代器的代碼,還原成XML文件的格式該如何書寫呢?
<Composite>
<QueryPanel>
<DropDownList name="" type=""/>
<DropDownList name="" type=""/>
<DropDownList name="" type=""/>
<TextBox name="" value="" />
<TextBox name="" value="" />
<TextBox name="" value="" />
<ComboBox name="" value="" />
<ComboBox name="" value="" />
<ComboBox name="" value="" />
<Button name="" value="" />
<Button name="" value="" />
<Button name="" value="" />
</QueryPanel>
</Composite>
對于上面的XML文件,那么我們如何獲取到這個XML文件中的某些類型的節點呢?比如我如何獲取到TextBox呢?這時候我們可以使用Xpath語法來進行選擇:
{
string xPath = "/Composite/QueryPanel/TextBox";
private XmlNodeList list = null;
public XmlNodeList GetNodes()
{
System.Xml.XmlDocument doc = new XmlDocument();
doc.LoadXml("Composite.xml");
list = doc.SelectNodes(xPath);
return list;
}
public List<Control> GetControls()
{
List<Control> listControls = new List<Control>();
foreach (XmlNode node in list)
{
System.Windows.Forms.TextBox textBox = new TextBox();
textBox.Name = node.Attributes["name"].Value.ToString();
listControls.Add(textBox);
}
return listControls;
}
}
基于上面的形式我們也可以完成組合模式的要求,通過上面的講解,我們應該對組合模式有了一定的了解。
七、組合模式使用總結
通過上面的簡單講解,我們知道了,組合模式意圖是通過整體與局部之間的關系,通過樹形結構的形式進行組織復雜對象,屏蔽對象內部的細節,對外展現統一的方式來操作對象,是我們處理更復雜對象的一個手段和方式。本文以查詢控件為例,說明了,查詢控件內部的組成元素,及如何操作內部的組成元素,包括添加元素,刪除和處理相應事件的Handler,當然組合模式的作用遠比這些強大,后面我們肯定會在一些實例代碼中運用到組合模式的。組合模式如果在條件允許的情況下,我們盡量使用組合模式來處理復雜對象,遠比通過繼承出來的對象來的有效。
由于本人水平有限,加之理解有誤,錯誤之處還請大家批評指出,多謝大伙的支持和關注!