我們上篇主要講述了結(jié)構(gòu)型模式中的外觀模式,外觀模式作為結(jié)構(gòu)型模式中的一個(gè)簡(jiǎn)單又實(shí)用的模式,外觀模式通過封裝細(xì)節(jié)來提供大粒度的調(diào)用,直接的好處就是,封裝細(xì)節(jié),提供了應(yīng)用寫程序的可維護(hù)性和易用性。外觀模式一般應(yīng)用在系統(tǒng)架構(gòu)的服務(wù)層中,當(dāng)我們是多個(gè)不同類型的客戶端應(yīng)用程序時(shí),比如一個(gè)系統(tǒng)既可以在通過Web的形式訪問,也可以通過客戶端應(yīng)用程序的形式時(shí),可能通過外觀模式來提供遠(yuǎn)程服務(wù),讓應(yīng)用程序進(jìn)行遠(yuǎn)程調(diào)用,這樣通過外觀形式提供服務(wù),那么不管是什么樣的客戶端都訪問一致的外觀服務(wù),那么以后就算是我們的應(yīng)用服務(wù)發(fā)生變化,那么我們不需要修改沒一個(gè)客戶端應(yīng)用的調(diào)用,只需要修改相應(yīng)的外觀應(yīng)用即可。
我們主要是講述了以下的幾種情況,使用外觀模式可能更適合:
1、我們?cè)谑褂玫谌筋悗旎蛘逜PI的時(shí)候,我們通過本地的API接口的封裝,來完成對(duì)第三方API接口的粗粒度外觀對(duì)象,通過這個(gè)外觀對(duì)象可以很容易的完成服務(wù)的調(diào)用。
2、我們?cè)诩軜?gòu)設(shè)計(jì)的過程中,一次的功能訪問可能需要同時(shí)的調(diào)用很多個(gè)對(duì)象,那么如果我們?cè)诜?wù)調(diào)用的時(shí)候,能夠在應(yīng)用程序調(diào)用中一次就能完成所有要同時(shí)調(diào)用的對(duì)象那該多好啊,外觀模式無疑是最好的原則,特別是在分布式應(yīng)用中,通過遠(yuǎn)程調(diào)用服務(wù),通過外觀模式降低應(yīng)用程序與服務(wù)的交互次數(shù),同時(shí)可以降低應(yīng)用程序的復(fù)雜性。
本文將會(huì)講述結(jié)構(gòu)性模式中的另外一個(gè)常用的模式-組合模式,我們平時(shí)在面向?qū)ο蟮脑O(shè)計(jì)中,我想有一個(gè)原則經(jīng)常被提及就是,我們?cè)谠O(shè)計(jì)的時(shí)候,對(duì)象組合>類的繼承,本篇將會(huì)將結(jié)合簡(jiǎn)單的實(shí)例來說明這方面的優(yōu)勢(shì),并且完成對(duì)組合模式的主題思想的掌握。我們這樣來簡(jiǎn)單的理解組合模式,組合模式就是把一些現(xiàn)有的對(duì)象或者元素,經(jīng)過組合后組成新的對(duì)象,新的對(duì)象提供內(nèi)部方法,可以讓我們很方便的完成這些元素或者內(nèi)部對(duì)象的訪問和操作。我們也可以把組合對(duì)象理解成一個(gè)容器,容器提供各種訪問其內(nèi)部對(duì)象或者元素的API,我們只需要使用這些方法就可以操作它了。那么對(duì)象組合相比繼承的優(yōu)勢(shì)有哪些呢?可能具體的優(yōu)勢(shì),也不是一句二句就能表述清楚的,還是我們來看看圖形的可視化的描述吧。
a、上篇回顧。
b、摘要。
c、本文大綱。
d、組合模式的特點(diǎn)及使用場(chǎng)景。
e、組合模式的經(jīng)典實(shí)現(xiàn)。
f、組合模式的其他方案。
g、原型模式使用總結(jié)。
組合模式是將一系列對(duì)象組合成樹形結(jié)構(gòu)用來表示整體和部分之間的關(guān)系,組合模式的主要目的是達(dá)到,訪問組合對(duì)象和訪問單個(gè)對(duì)象具有一致性。這里的組合對(duì)象比較特殊,本身他可以是由其他的對(duì)象組合而成,同時(shí),這個(gè)組合對(duì)象又可以是組成更復(fù)雜對(duì)象的一個(gè)部分。我們來舉個(gè)例子來說明吧,可能更直觀。
我們一般在如下場(chǎng)景中使用組合模式比較方便:
1、我們有的時(shí)候想用戶使用一個(gè)復(fù)雜對(duì)象像使用簡(jiǎn)單對(duì)象一樣的方式去訪問,并且用戶同意使用對(duì)象內(nèi)部的所有的對(duì)象時(shí),我們可以考慮使用該模式。這個(gè)怎么理解呢?我們使用復(fù)雜對(duì)象像使用簡(jiǎn)單對(duì)象一樣的方式去訪問的話,那么我們可以使用組合對(duì)象,我們把這些簡(jiǎn)單的對(duì)象進(jìn)行組合,用組合對(duì)象進(jìn)行包裝,并且提供相應(yīng)的操作組合對(duì)象內(nèi)部的方法。結(jié)合上圖中的例子,我們可以這樣理解,我們把查詢組件封裝成一個(gè)用戶控件,然后可以在其他的頁面中進(jìn)行調(diào)用。這個(gè)時(shí)候,我們可能考慮如何分部頁面,如何能夠動(dòng)態(tài)的維護(hù)界面上的控件,是否顯示,控件里面顯示的文本和值是什么?等等,這些都是我們可以考慮的元素,那么我們?nèi)绾蝸碜瞿??提供下面的幾個(gè)方法。我還是給出圖來說話吧:
public QueryControl()
{
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>
/// 移除指定鍵值的對(duì)象
/// </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>
/// 根據(jù)選中的條件生成SQL語句
/// </summary>
private void CreateSQL()
{
throw new NotImplementedException();
}
上面的代碼很簡(jiǎn)單就是給出了基本的思路,并不是完整的功能。我們來看其他的可能會(huì)用到組合模式的情況。
2、如果有的時(shí)候,我們希望用戶不了解自己使用的對(duì)象有多復(fù)雜,并且組合對(duì)象可以的內(nèi)部可以自由的變化和組合,但是不會(huì)影響到客戶應(yīng)用程序使用這個(gè)組合對(duì)象,如果項(xiàng)目中需要新增一個(gè)組合對(duì)象的時(shí)候,客戶調(diào)用的程序還是一樣,不會(huì)因?yàn)檫@個(gè)組合對(duì)象發(fā)生變更而發(fā)生變化。組合模式在解決整體和部分之間的問題應(yīng)用很廣泛,也可以降低系統(tǒng)的復(fù)雜度,因?yàn)槲覀兛梢园褟?fù)雜的組件看作另一個(gè)組件的組成部分來處理。就像上面的查詢組件,那么我們可以把查詢組件作為分頁組件的一個(gè)部分來處理。我這里就不給出示例代碼了通過上面的情況,我們可以大概的知道,組合模式的使用場(chǎng)景。下面我們就要結(jié)合實(shí)例說明組合模式的用處了。
我們先來看看使用經(jīng)典的組合模式來實(shí)現(xiàn)我們上面說的查詢控件吧,看看和其他的方案有什么樣的不同,不過大體的思路都是一致的,把其他的對(duì)象進(jìn)行組合成復(fù)雜的對(duì)象,然后提供操作內(nèi)部對(duì)象的方法,具體的方式可以很多。我們這里還是以上面的查詢組件和分頁組件為例來說明具體的實(shí)現(xiàn)思路,當(dāng)然我這里只是給出核心代碼,但不是完整的代碼:
我們先給出控件內(nèi)部的完整定義:
public QueryPanel()
{
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>
/// 移除指定鍵值的對(duì)象
/// </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>
/// 根據(jù)選中的條件生成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;
}
}
}
}
這時(shí)候我們?nèi)绻胍貙懮厦娴姆桨?,可能有時(shí)候我們的查詢控件需要基于上面的幾個(gè)基本的操作方法進(jìn)行重寫,完成自定義的組合對(duì)象的操作:
public partial class QueryPanelText : QueryPanel
{
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();
}
}
當(dāng)然我上面并沒有改變對(duì)象的任何行為,這里定義了公共的行為,有的時(shí)候我們需要限制一些對(duì)象內(nèi)部對(duì)象的某些行為,這時(shí)候我們通過繼承公共的對(duì)象來重寫對(duì)象的行為來完成。例如從組合對(duì)象衍生出來的某些組合對(duì)象,可能不具有父類的某些行為時(shí),我們可以通過重寫父類的行為來完成自定義的操作。
有的時(shí)候,我們可能并不關(guān)心對(duì)象中的所有的元素,我們只是操作其中的具有同類特征的對(duì)象,比如對(duì)于上面的查詢控件中的對(duì)象,我們可能想要獲取設(shè)置條件的控件項(xiàng),或者說我們相應(yīng)控制文本框或者下拉框的樣式或者高度等等,這個(gè)時(shí)候,我們需要對(duì)組合對(duì)象內(nèi)的元素就行篩選。這個(gè)時(shí)候,我們可能可以采用過濾器來實(shí)現(xiàn)這樣的功能。
對(duì)于上面的結(jié)果,我們可能需要獲取界面中所有的文本框,或者下拉框,或者其他類型的控件等,這時(shí)候我們可以如下方式來做,通過接口定義,根據(jù)傳入的不同的過濾器,返回過濾后的結(jié)果集合。我們先定義一個(gè)過濾器接口:
public interface ISelectRule
{
bool IsMatch(Control control);
}
我們這里定義一個(gè)文本過濾器的實(shí)現(xiàn)。
public class TextSelect : ISelectRule
{
#region ISelectRule 成員
public bool IsMatch(System.Windows.Forms.Control control)
{
if (control is System.Windows.Forms.TextBox)
return true;
return false;
}
#endregion
}
我們來看看具體的查詢控件內(nèi)部的獲取內(nèi)部元素的方法:
/// <summary>
/// 獲取集合中的所有控件集合
/// </summary>
/// <returns></returns>
public IEnumerable<Control> GetControlList(ISelectRule rule)
{
foreach (Control control in base.GetControls().Values)
{
if (rule.IsMatch(control))
yield return control;
}
}
通過上面的幾行代碼的改進(jìn),我們就可以完成對(duì)內(nèi)部的組合對(duì)象的改進(jìn),這樣就可以完成根據(jù)不同的查詢規(guī)則,來返回不同的內(nèi)部對(duì)象集合。
通過上面的迭代器來封裝我們的內(nèi)部組合對(duì)象,我們通過迭代器來完成組合對(duì)象的過濾。我們還可以通過XML文件來組織我們的對(duì)象組合的結(jié)構(gòu),XML文件先天性的優(yōu)勢(shì)就是結(jié)構(gòu)化的語言,特別適合這樣的整體與局部關(guān)系的結(jié)構(gòu),所以我們的組合對(duì)象也可以通過XML文件來組織,可以將復(fù)雜對(duì)象設(shè)置為一個(gè)復(fù)雜的節(jié)點(diǎn),該節(jié)點(diǎn)下包含多個(gè)對(duì)象時(shí),我們只需要將每個(gè)對(duì)象設(shè)置為一個(gè)節(jié)點(diǎn),通過XPath語言完成查詢。
6.2XML文件格式的組合模式
使用XML文件來完成組合模式有如下優(yōu)點(diǎn):
1、我們有系統(tǒng)提供的類庫自動(dòng)完成對(duì)XML文件的操作和訪問。
2、XML提供了基于XPath解析的查詢語言。
也有一些點(diǎn),就是操作XML文件,調(diào)試起來會(huì)比較麻煩,而且進(jìn)行代碼編寫和測(cè)試是個(gè)不太方便的事情。
我們來看看如果我們把上面的獲取迭代器的代碼,還原成XML文件的格式該如何書寫呢?
<?xml version="1.0" encoding="utf-8" ?>
<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>
對(duì)于上面的XML文件,那么我們?nèi)绾潍@取到這個(gè)XML文件中的某些類型的節(jié)點(diǎn)呢?比如我如何獲取到TextBox呢?這時(shí)候我們可以使用Xpath語法來進(jìn)行選擇:
public class XMLSelect
{
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;
}
}
基于上面的形式我們也可以完成組合模式的要求,通過上面的講解,我們應(yīng)該對(duì)組合模式有了一定的了解。
通過上面的簡(jiǎn)單講解,我們知道了,組合模式意圖是通過整體與局部之間的關(guān)系,通過樹形結(jié)構(gòu)的形式進(jìn)行組織復(fù)雜對(duì)象,屏蔽對(duì)象內(nèi)部的細(xì)節(jié),對(duì)外展現(xiàn)統(tǒng)一的方式來操作對(duì)象,是我們處理更復(fù)雜對(duì)象的一個(gè)手段和方式。本文以查詢控件為例,說明了,查詢控件內(nèi)部的組成元素,及如何操作內(nèi)部的組成元素,包括添加元素,刪除和處理相應(yīng)事件的Handler,當(dāng)然組合模式的作用遠(yuǎn)比這些強(qiáng)大,后面我們肯定會(huì)在一些實(shí)例代碼中運(yùn)用到組合模式的。組合模式如果在條件允許的情況下,我們盡量使用組合模式來處理復(fù)雜對(duì)象,遠(yuǎn)比通過繼承出來的對(duì)象來的有效。
由于本人水平有限,加之理解有誤,錯(cuò)誤之處還請(qǐng)大家批評(píng)指出,多謝大伙的支持和關(guān)注!
聯(lián)系客服