小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

.Net中的設計模式——Composite模式

 ljjzlm 2006-08-04

.Net中的設計模式——Composite模式

Filed under: .Net Framework, Design & Pattern — bruce zhang @ 1:17 pm

一、模式概述

描述Composite模式的最佳方式莫過于樹形圖。從抽象類或接口為根節(jié)點開始,然后生枝發(fā)芽,以形成樹枝節(jié)點和葉結(jié)點。因此,Composite模式通常用來描述部分與整體之間的關系,而通過根節(jié)點對該結(jié)構(gòu)的抽象,使得客戶端可以將單元素節(jié)點與復合元素節(jié)點作為相同的對象來看待。

由于Composite模式模糊了單元素和復合元素的區(qū)別,就使得我們?yōu)檫@些元素提供相關的操作時,可以有一個統(tǒng)一的接口。例如,我們要編寫一個字處理軟件,該軟件能夠處理文字,對文章進行排版、預覽、打印等功能。那么,這個工具需要處理的對象,就應該包括單個的文字、以及由文字組成的段落,乃至整篇文檔。這些對象從軟件處理的角度來看,對外的接口應該是一致的,例如改變文字的字體,改變文字的位置使其居中或者右對齊,也可以顯示對象的內(nèi)容,或者打印。而從內(nèi)部實現(xiàn)來看,我們對段落或者文檔進行操作,實質(zhì)上也是對文字進行操作。從結(jié)構(gòu)來看,段落包含了文字,文檔又包含了段落,是一個典型的樹形結(jié)構(gòu)。而其根節(jié)點正是我們可以抽象出來暴露在外的統(tǒng)一接口,例如接口IElement:

composite1.GIF

既然文字、段落、文檔都具有這些操作,因此它們都可以實現(xiàn)IElement接口:

從上圖可以看到,對象Word、Paragraph、Document均實現(xiàn)了IElement接口,但Paragraph和Document與Word對象不同的是,這兩者處除了實現(xiàn)了IElement接口,它們還與IElement接口對象之間具有聚合的關系,且是一對多。也就是說Paragraph與Document對象內(nèi)可以包含0個到多個IElement對象,這也是與前面對字處理軟件分析獲得的結(jié)果是一致的。

從整個結(jié)構(gòu)來看,完全符合樹形結(jié)構(gòu)的各個要素,接口IElement是根節(jié)點,而Paragraph和Document類為枝節(jié)點,Word對象為葉節(jié)點。既然作為枝節(jié)點,它就具有帶葉節(jié)點的能力,從上圖的聚合關系中我們體現(xiàn)出了這一點。也就是說,Paragraph和Document類除了具有排版、打印方面的職責外,還能夠添加、刪除葉節(jié)點的操作。那么這些操作應該放在哪里呢?

管理對子節(jié)點的管理,Composite模式提供了兩種方式:一個是透明方式,也就是說在根節(jié)點中聲明所有用來管理子元素的方法,包括Add()、Remove()等方法。這樣一來,實現(xiàn)根節(jié)點接口的子節(jié)點同時也具備了管理子元素的能力。這種實現(xiàn)策略,最大的好處就是完全消除了葉節(jié)點和枝節(jié)點對象在抽象層次的區(qū)別,它們具備完全一致的接口。而缺點則是不夠安全。由于葉節(jié)點本身不具備管理子元素的能力,因此提供的Add()、Remove()方法在實現(xiàn)層次是無意義的。但客戶端調(diào)用時,卻看不到這一點,從而導致在運行期間有出錯的可能。

另一種策略則是安全方式。與透明方式剛好相反,它只在枝節(jié)點對象里聲明管理子元素的方法,由于葉節(jié)點不具備這些方法,當客戶端在操作葉節(jié)點時,就不會出現(xiàn)前一種方式的安全錯誤。然而,這種實現(xiàn)方式,卻導致了葉節(jié)點和枝節(jié)點接口的不完全一致,這給客戶端操作時帶來了不便。

這兩種方式各有優(yōu)缺點,我們在實現(xiàn)時,應根據(jù)具體的情況,作出更加合理的抉擇。在字處理軟件一例中,我選擇了安全方式來實現(xiàn),因為對于客戶端而言,在調(diào)用IElement接口時,通常是將其視為可被排版、打印等操作的對象。至于為Paragraph和Document對象添加、刪除子對象,往往是一種初始化的行為,完全可以放到一個單獨的模塊中。根據(jù)單一職責原則(SRP),我們沒有必要讓IElement接口負累太重。所以,我們需要對上圖作稍許的修改,在Paragraph和Document對象中增加Add()和Remove()方法:

以下是IElement對象結(jié)構(gòu)的實現(xiàn)代碼:
public interface IElement
{
      void ChangeFont(Font font);
      void Show();
      //其他方法略;
}
public class Word
{
 public void ChangeFont(Font font)
 {
      this.font = font;
 }
 public void Show()
 {
      Console.WriteLine(this.ToString());
 }
 //其他方法略;
}
public class Paragraph
{
 private ArrayList elements = new ArrayList();
 public void Add(IElement element)
 {
      elements.Add(element);
 }
 public void Remove(IElement element)
 {
      elements.Remove(element);
 }
 public void ChangeFont(Font font)
 {
      foreach (IElement element in elements)
      {
          element.ChangeFont(font);
  }
 }
 public void Show()
 {
      foreach (IElement element in elements)
      {
          element.Show(font);
  }
 }
 //其他方法略;
}
//Document類略;

實際上,我們在為葉節(jié)點實現(xiàn)Add(),Remove()方法時,還需要考慮一些異常情況。例如在Paragraph類中,添加的子元素就不能是Document對象和Paragraph對象。所以在添加IElement對象時,還需要做一些條件判斷,以確定添加行為是否正確,如果錯誤,應拋出異常。

采用Composite模式,我們將Word、Paragraph、Document抽象為IElement接口。雖然各自內(nèi)部的實現(xiàn)并不相同,枝節(jié)點和葉節(jié)點的實質(zhì)也不一樣,但對于調(diào)用者而言,是沒有區(qū)別的。例如在類WordProcessor中,包含一個GetSelectedElement()靜態(tài)方法,它能夠獲得當前選擇的對象:
public class WordProcessor
{
 public static IElement GetSelectedElement(){……}
}

對于字處理軟件的UI來說,如果要改變選中對象的字體,則可以在命令按鈕cmdChangeFont的Click事件中寫下如下代碼:
public void cmdChangeFont_Click(object sender, EventArgs e)
{
    WordProcessor.GetSelectedElement().ChangeFont(currentFont);
}

不管當前選中的對象是文字、段落還是整篇文檔,對于UI而言,操作都是完全一致的,根本不需要去判斷對象的類別。因此,如果在Business Layer的類庫設計時,采用Composite模式,將極大地簡化UI表示層的開發(fā)工作。此外,應用該模式也較好的支持項目的可擴展性。例如,我們?yōu)镮Element接口增加了Sentence類,對于前面的例子而言,只需要修改GetSelectedElement()方法,而cmdChangeFont命令按鈕的Click事件以及Business Layer類庫原有的設計,都不需要做任何改變。這也符合OO的開放-封閉原則(OCP),即對于擴展是開放的(Open for extension),對于更改則是封閉的(Closed for modification)。

二、.Net Framework中的Composite模式

在.Net中,最能體現(xiàn)Composite模式的莫過于Windows或Web的控件。在這些控件中,有的包含子控件,有的則不包含且不能包含子控件,這正好符合葉節(jié)點和枝節(jié)點的含義。所有Web控件的基類為System.Web.UI.Contril類(如果是Windows控件,則基類為System.Windows.Forms.Control類)。其子類包含有HtmlControl、HtmlContainerControl等。按照Composite模式的結(jié)構(gòu),枝節(jié)點和葉節(jié)點屬于根節(jié)點的不同分支,同時枝節(jié)點與根節(jié)點之間應具備一個聚合關系,可以通過Add()、Remove()方法添加和移除其子節(jié)點。設定HtmlControl為葉節(jié)點,而HtmlContaiinerControl為枝節(jié)點,那么采用透明方式的設計方法,在.Net中控件類的結(jié)構(gòu),就應該如下圖所示:

雖然根據(jù)透明方式的Composite模式,HtmlControl類與其父類Control之間也應具備一個聚合關系,但實質(zhì)上該類并不具備管理子控件的職責,因此我在類圖中忽略了這個關系。此時,HtmlControl類中的Add()、Remove()方法,應該為空,或者拋出一個客戶端能夠捕獲的異常。

然而,從具體實現(xiàn)來考慮,由于HtmlControl類和HtmlContainerControl類在實現(xiàn)細節(jié)層次,區(qū)別僅在于前者不支持子控件,但從控件本身的功能來看,很多行為是相同或者相近的。例如HtmlControl類的Render()方法,調(diào)用了方法RenderBeginTag()方法:
protected override void Render(HtmlTextWriter writer)
{
      this.RenderBeginTag(writer);
}
protected virtual void RenderBeginTag(HtmlTextWriter writer)
{
      writer.WriteBeginTag(this.TagName);
      this.RenderAttributes(writer);
      writer.Write(’>‘);
}

而HtmlContainerControl類也具有Render()方法,在這個方法中也調(diào)用了RenderBeginTag()方法,且RenderBeginTag方法的實現(xiàn)和前者完全一致:
protected override void Render(HtmlTextWriter writer)
{
      this.RenderBeginTag(writer);
      this.RenderChildren(writer);
      this.RenderEndTag(writer);
}

按照上面的結(jié)構(gòu),由于HtmlControl和HtmlContainerControl之間并無繼承關系,這就要求兩個類中,都要重復實現(xiàn)RenderBeginTag()方法,從而導致產(chǎn)生重復代碼。根據(jù)OO的特點,解決的辦法,就是讓HtmlContainerControl繼承自HtmlControl類(因為HtmlContainerControl的接口比HtmlControl寬,所以只能令HtmlContainerControl作為子類),并讓RenderBeginTag()方法成為HtmlControl類的protected方法,子類HtmlContainerControl可以直接調(diào)用這個方法。然而與之矛盾的是,HtmlContainerControl卻是一個可以包含子控件的枝節(jié)點,而HtmlControl則是不能包含子控件的葉節(jié)點,那么這樣的繼承關系還成立嗎?

HtmlControl類對Add()方法和Remove()方法的重寫后,這兩個方法內(nèi)容為空。由于HtmlContainerControl類繼承HtmlControl類,但我們又要求它的Add()和Remove()方法和Control類保持一致,而父類HtmlControl已經(jīng)重寫這兩個方法,此時是無法直接繼承來自父類的方法的。以上是采用透明方式的設計。

如果采用安全方式,仍然有問題。雖然在HtmlControl類中不再有Add()和Remove()方法,但由于Control類和HtmlContainerControl類都允許添加子控件,它們包含的Add()、Remove()方法,只能分別實現(xiàn)。這樣的設計必然會導致重復代碼。這也是與我們的期望不符的。

那么在.Net中,Control類究竟是怎樣實現(xiàn)的呢?下面,我將根據(jù).Net實現(xiàn)Control控件的源代碼,來分析Control控件的真實結(jié)構(gòu),以及其具體的實現(xiàn)細節(jié)。

三、深入分析.Net中的Composite模式

首先,我們來剖析Web控件的基類Control類的內(nèi)部實現(xiàn):

public class Control : IComponent, IDisposable, IParserAccessor, IDataBindingsAccessor
{
       // Events;略  
       // Methods
 public Control()
 {
             if (this is INamingContainer)
             {
                    this.flags[0×80] = true;
             }
 }
 public virtual bool HasControls()
 {
              if (this._controls != null)
              {
                    return (this._controls.Count > 0);
              }
              return false;
 }
 public virtual void DataBind()
        {
              this.OnDataBinding(EventArgs.Empty);   
              if (this._controls != null)           
       {                
              string text1 = this._controls.SetCollectionReadOnly(”Parent_collections_readonly”);
                        int num1 = this._controls.Count;
                 for (int num2 = 0; num2 < num1; num2++)                 
   {
                         this._controls[num2].DataBind();
                   }
                   this._controls.SetCollectionReadOnly(text1);
              }
 }
        protected virtual void Render(HtmlTextWriter writer)
 {
              this.RenderChildren(writer);
 }
 protected virtual ControlCollection CreateControlCollection()
 {
              return new ControlCollection(this);
 }
       // Properties
 public virtual ControlCollection Controls
 {
             get
             {
                   if (this._controls == null)
                   {
                         this._controls = this.CreateControlCollection();
                   }
                   return this._controls;
             }
 }
        // Fields
        private ControlCollection _controls;
}

Control基類中的屬性和方法很多,為清晰起見,我只保留了幾個與模式有關的關鍵方法與屬性。在上述的源代碼中,我們需要注意幾點:

1、Control類不是抽象類,而是具體類。這是因為在設計時,我們可能會創(chuàng)建Control類型的實例。根據(jù)這一點來看,這并不符合OOP的要求。一般而言,作為抽象出來的基類,必須定義為接口或抽象類。不過在實際的設計中,也不應拘泥于這些條條框框,而應審時度勢,根據(jù)實際的情況來抉擇最佳的設計方案。
2、公共屬性Controls為ControlCollection類型,且該屬性為virtual屬性。也就是說,這個屬性可以被它的子類override。同時,該屬性為只讀屬性,在其get訪問器中,調(diào)用了方法CreateControlCollection();這個方法為protected虛方法,默認的實現(xiàn)是返回一個ControlCollection實例。
3、方法HasControls(),功能為判斷Control對象是否有子控件。它判斷的依據(jù)是根據(jù)私有字段_controls(即公共屬性Controls)的Count值。但是需要注意的是,通過HasControls()方法的返回值,并不能決定對象本身屬于葉節(jié)點,還是枝節(jié)點。因為即使是枝節(jié)點其內(nèi)部仍然可以不包含任何子對象。
4、 方法DataBind()的實現(xiàn)中,首先調(diào)用了自身的OnDataBinding()方法,然后又遍歷了Controls中的所有控件,并調(diào)用其DataBind()方法。該方法屬于控件的共有行為,從這里可以看出不管是作為葉節(jié)點的控件,還是作為枝節(jié)點的控件,它們都實現(xiàn)統(tǒng)一的接口。對于客戶端調(diào)用而言,枝節(jié)點和葉節(jié)點是沒有區(qū)別的。
5、 Control類的完整源代碼中,并不存在Add()、Remove()等類似的方法,以提供添加和移除子控件的功能。事實上,繼承Control類的所有子類均不存在Add()、Remove()等方法。

顯然,在Control類的定義和實現(xiàn)中,值得我們重視的是公共屬性Controls的類型ControlCollection。顧名思義,該類必然是一個集合類型。是否有關子控件的操作,都是在ControlCollection類型中實現(xiàn)呢?我們來分析一下ControlCollection的代碼:
public class ControlCollection : ICollection, IEnumerable
{
       // Methods
 public ControlCollection(Control owner)
 {
               this._readOnlyErrorMsg = null;
               if (owner == null)
               {
                       throw new ArgumentNullException("owner");
               }
               this._owner = owner;
        }
 public virtual void Add(Control child)
        {
               if (child == null)
               {
                throw new ArgumentNullException("child");
               }
               if (this._readOnlyErrorMsg != null)
                   {
                throw new HttpException(HttpRuntime.FormatResourceString(this._readOnlyErrorMsg));
               }
               if (this._controls == null)
               {
                   this._controls = new Control[5];
               }
               else if (this._size >= this._controls.Length)
               {
                   Control[] controlArray1 = new Control[this._controls.Length * 4];
                   Array.Copy(this._controls, controlArray1, this._controls.Length);
                   this._controls = controlArray1;
               }
               int num1 = this._size;
               this._controls[num1] = child;
               this._size++;
               this._version++;
               this._owner.AddedControl(child, num1);
        }
        public virtual void Remove(Control value)
 {
               int num1 = this.IndexOf(value);
               if (num1 >= 0)
               {
                this.RemoveAt(num1);
               }
        }
        // Indexer
 public virtual Control this[int index]
 {
               get
               {
                    if ((index < 0) || (index >= this._size))
                    {
                         throw new ArgumentOutOfRangeException(”index”);
                    }
                    return this._controls[index];
               }
 }
 // Properties    
 public int Count
 {
        get
               {
                    return this._size;
               }
        }
 protected Control Owner
 {
               get
               {
                    return this._owner;
               }
 }
        protected Control Owner { get; }   
        // Fields
        private Control[] _controls;
        private const int _defaultCapacity = 5;
        private const int _growthFactor = 4;
 private Control _owner;    
}

一目了然,正是ControlCollection的Add()、Remove()方法完成了對子控件的添加和刪除。例如:
Control parent = new Control();
Control child = new Child();
//添加子控件child;
parent.Controls.Add(child);
//移除子控件child;
parent.Controls.Remove(child);

為什么要專門提供ControlCollection類型來管理控件的子控件呢?首先,作為類庫使用者,自然希望各種類型的控件具有統(tǒng)一的接口,尤其是自定義控件的時候,不希望自己重復定義管理子控件的操作;那么采用透明方式自然是最佳方案。然而,在使用控件的時候,安全也是需要重點考慮的,如果不考慮子控件管理的合法性,一旦使用錯誤,會導致整個應用程序出現(xiàn)致命錯誤。從這樣的角度考慮,似乎又應采用安全方式。這里就存在一個抉擇。故而,.Net在實現(xiàn)Control類庫時,利用了職責分離的原則,將控件對象管理子控件的屬性與行為和控件本身分離,并交由單獨的ControlCollection類負責。同時采用聚合而非繼承的方式,以一個公共屬性Controls,存在于Control類中。這種方式,集保留了透明方式和安全方式的優(yōu)勢,又摒棄了這兩種方式固有的缺陷,因此我名其為“復合方式”。

“復合方式”的設計,其對安全的保障,不僅僅是去除了Control類關于子控件管理的統(tǒng)一接口,同時還通過異常管理的方式,在ControlCollection類的子類中實現(xiàn):
public class EmptyControlCollection : ControlCollection
{
        // Methods
 public EmptyControlCollection(Control owner) : base(owner)
 {}
 public override void Add(Control child)
 {
            this.ThrowNotSupportedException();
 }                         
        private void ThrowNotSupportedException()
        {
            throw new HttpException(HttpRuntime.FormatResourceString(”Control_does_not_allow_children”, base.Owner.GetType().ToString()));
        }
}

EmptyControlCollection繼承了ControlCollection類,并重寫了Add()等添加子控件的方法,使其拋出一個異常。注意,它并沒有重寫父類的Remove()方法,這是因為ControlCollection類在實現(xiàn)Remove()方法時,對集合內(nèi)的數(shù)據(jù)進行了非空判斷。而在EmptyControlCollection類中,是不可能添加子控件的,直接調(diào)用父類的Remove()方法,是不會出現(xiàn)錯誤的。

既然管理子控件的職責由ControlCollection類型負責,且Control類中的公共屬性Controls即為ControlCollection類型。所以,對于控件而言,如果是樹形結(jié)構(gòu)中的葉節(jié)點,它不能包含子控件,它的Controls屬性就應為EmptyControlCollection類型,假如用戶調(diào)用了Controls的Add()方法,就會拋出異常。如果控件是樹形結(jié)構(gòu)中的枝節(jié)點,它支持子控件,那么Controls屬性就是ControlCollection類型。究竟是枝節(jié)點還是葉節(jié)點,決定權在于公共屬性Controls:
public virtual ControlCollection Controls
{
      get
      {
             if (this._controls == null)
             {
                   this._controls = this.CreateControlCollection();
             }
             return this._controls;
      }
}

在屬性的get訪問器中,調(diào)用了protected方法CreateControlCollection(),它創(chuàng)建并返回了一個ControlCollection實例:
protected virtual ControlCollection CreateControlCollection()
{
     return new ControlCollection(this);
}

很明顯,在Control基類實現(xiàn)Controls屬性時,采用了Template Method模式,它推遲了ControlCollection的創(chuàng)建,將決定權交給了CreateControlCollection()方法。

如果我們需要定義一個控件,要求它不能管理子控件,就重寫CreateControlCollection()方法,返回EmptyControlCollection對象:

protected override ControlCollection CreateControlCollection()
{
     return new EmptyControlCollection(this);
}
 
現(xiàn)在再回過頭來看HtmlControl和HtmlContainerControl類。根據(jù)前面的分析,我們要求HtmlContainerControl繼承HtmlControl類,同時,HtmlContainerControl應為枝節(jié)點,能夠管理子控件;HtmlControl則為葉節(jié)點,不支持子控件。通過引入ControlCollection類和其子類EmptyControlCollection,以及Template Method模式后,這些類之間的關系與結(jié)構(gòu)如下所示:

HtmlContainerControl繼承了HtmlControl類,這兩個類都重寫了自己父類的protected方法CreateControlCollection()。HtmlControl類,該方法返回EmptyControlCollection對象,使其成為了不包含子控件的葉節(jié)點;HtmlContainerControl類中,該方法則返回ControlCollection對象,從而被賦予了管理子控件的能力,成為了枝節(jié)點:
public abstract class HtmlControl : Control, IAttributeAccessor
{
        // Methods
 protected override ControlCollection CreateControlCollection()
 {
            return new EmptyControlCollection(this);
 }
}
public abstract class HtmlContainerControl : HtmlControl
{
       // Methods
 protected override ControlCollection CreateControlCollection()
 {
            return new ControlCollection(this);
 }
}

HtmlControl和HtmlContainerControl類均為抽象類。要定義它們的子類,如果不重寫其父類的CreateControlCollection()方法,那么它們的Controls屬性,就與父類完全一致。例如HtmlImage控件繼承自HtmlControl類,該控件不能添加子控件;而HtmlForm控件則繼承自HtmlContainerControl類,顯然,HtmlForm控件是支持添加子控件的操作的。

.Net的控件設計采用Composite模式的“復合方式”,較好地將控件的透明性與安全性結(jié)合起來,它的特點是:

1、在統(tǒng)一接口中消除了Add()、Remove()等子控件的管理方法,而由ControlCollection類實現(xiàn),同時通過EmptyControlCollection類保障了控件進一步的安全;
2、控件能否管理子控件,不由繼承的層次決定;而是通過重寫CreateControlCollection()方法,由Controls屬性的真正類型來決定。

如此一來,要定義自己的控件就更加容易。我們可以任意地擴展自己的控件類。不管繼承自Control,還是HtmlControl或HtmlContainerControl,都可以輕松地定義出具有枝節(jié)點或葉節(jié)點屬性的新控件。如果有新的需求要求改變管理子控件的方式,我們還可以定義繼承自ControlCollection的類,并在控件類的方法CreateControlCollection()中創(chuàng)建并返回它的實例。

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多