日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

條款19:定義并實現(xiàn)接口優(yōu)于繼承類型 - 《Effective C#中文版:改善C#程序的...

 coding 2010-07-23

C#語言引入了許多新的語法來表達(dá)程序設(shè)計。我們所選擇的技巧,實際上是向維護(hù)、擴(kuò)展和使用我們軟件的開發(fā)人員表達(dá)了我們的設(shè)計意圖。所有的C#類型都生存于.NET環(huán)境中。.NET環(huán)境對于所有類型的能力也都有某種假設(shè)。如果我們違反了這些假設(shè),那么類型不能正常工作的可能性就會大大增加。

本章的條款并不是要對軟件設(shè)計技巧進(jìn)行概要介紹——這方面的著作已經(jīng)不少。相反,本章主要探討如何更好地利用不同的C#語言特性,來表達(dá)我們的軟件設(shè)計意圖。C#語言的設(shè)計者們添加了許多語言特性,來讓我們更清晰地表達(dá)現(xiàn)代軟件設(shè)計中的各種慣用法(idiom)。某些語言特性之間的差別非常小,我們通常有許多選擇。選擇多剛開始看起來似乎是好事情,但是當(dāng)我們發(fā)現(xiàn)需要擴(kuò)展現(xiàn)有的程序時,區(qū)別就開始顯現(xiàn)了。我們首先要確保很好地理解本章中的各個條款,然后在應(yīng)用它們的時候,要對軟件未來可能的擴(kuò)展有一個清醒的認(rèn)識。

某些語法的改變使我們擁有了新的詞匯來表述日常的慣用法。屬性、索引器、事件和委托都是這樣例子,還有類與接口的區(qū)別:類定義類型,接口聲明行為?;惵暶黝愋?,同時定義一組相關(guān)類型所共有的行為。其他一些設(shè)計慣用法也由于垃圾收集器的引入而有所改變。而且,由于絕大多數(shù)變量都是引用類型,因此也會為我們的設(shè)計慣用法帶來一些變化。

本章的推薦條款將幫助大家選擇最自然的構(gòu)造來表達(dá)自己的軟件設(shè)計,從而使創(chuàng)建的軟件更易于維護(hù)、擴(kuò)展和使用。

條款19:定義并實現(xiàn)接口優(yōu)于繼承類型

抽象基類為類層次(class hierarchy)提供了一個共用的祖先類(ancestor)。接口則描述了一組可以由某個類型實現(xiàn)的緊湊的功能。每一個都有自己的用武之地,但用處各不相同。接口是一種按合同設(shè)計(design by contract)的方式:一個實現(xiàn)了某個接口的類型,必須提供接口中約定的方法實現(xiàn)。抽象基類則為一組相關(guān)的類型提供了一個共用的抽象。下面的表述雖然是陳詞濫調(diào),但是很有用:繼承意味著“is a”,接口意味著“behaves like”。這些表述之所以至今仍有生命力,是因為它們很好地描述了兩種構(gòu)造之間的差別:基類描述了對象是什么;接口描述了對象的行為方式。

接口描述了一組功能,或者說一個合同。我們可以在接口中為任何構(gòu)造創(chuàng)建占位符(placeholder):方法、屬性、索引器和事件。任何實現(xiàn)了接口的類型都必須為接口中定義的所有元素提供具體的實現(xiàn),即必須實現(xiàn)所有的方法,提供所有的屬性訪問器和索引器,并定義接口中定義的所有事件。我們應(yīng)該識別可重用的行為,并將它們提取出來定義在接口中。我們可以將接口用做函數(shù)的參數(shù),并返回值。由于不相關(guān)的類型可以共同實現(xiàn)一個接口,因此我們將有更多機(jī)會重用代碼。而且,實現(xiàn)一個接口對于開發(fā)人員來說,要比繼承一個我們創(chuàng)建的類型更加容易。

我們不能在接口中提供任何成員的實現(xiàn)。接口不能包含實現(xiàn),也不能包含任何具體的數(shù)據(jù)成員。接口是在聲明一種合同:所有實現(xiàn)了接口的類型都要負(fù)責(zé)履行其中的約定。

除了描述共同的行為外,抽象基類還可以為派生類型提供一些具體的實現(xiàn)。在抽象類中,我們可以指定數(shù)據(jù)成員、具體的方法、虛方法的實現(xiàn)、屬性、事件和索引器?;惪梢詫崿F(xiàn)一些具體的方法,因此可以為子類提供一些通用的可重用代碼。任何元素都可以為虛擬成員、抽象成員或者非虛成員。抽象基類可以為任何具體的行為提供一個實現(xiàn),而接口則不能。

這種實現(xiàn)重用還提供了另一種好處:如果向基類中添加一個方法,所有派生類都將自動隱含這個方法。從這個角度來看,基類為我們提供了一種隨時間推移可以有效擴(kuò)展多個類型功能的方式。通過向基類中添加并實現(xiàn)某種功能,所有的派生類都將立即擁有該功能。而向接口中添加一個成員,則會破壞所有實現(xiàn)了該接口的類。它們不會包含新的方法,并且不會再通過編譯。每一個具體的類型都必須更新自己,來實現(xiàn)新的成員。

在抽象基類和接口之間做選擇,實際上是一個如何隨著時間的推移更好地支持抽象的問題。接口的特點是比較穩(wěn)定:我們將一組功能封裝在一個接口中,作為其他類型的實現(xiàn)合同?;悇t可以隨著時間的推移進(jìn)行擴(kuò)展。這些擴(kuò)展將成為每個派生類的一部分。

上述兩種模型可以混合使用,從而允許類型在支持多個接口的同時,可以重用實現(xiàn)代碼。一個典型的例子是System.Collections.CollectionBase。該類提供了一個基類,使用它可以避免.NET集合類中缺乏類型安全的問題。同時,它也實現(xiàn)了幾個我們需要的接口:IList、ICollection和IEnumerable。另外,它還提供了一些受保護(hù)的方法,我們可以重寫它們來定制一些自己需要的行為。IList接口包含的Insert()方法會將一個新的對象添加到集合中。不用提供我們自己的Insert()實現(xiàn),我們就可以通過重寫CollectionBase類的OnInsert()或者OnInsertCcomplete()虛方法來處理一些事件。

public class IntList : System.Collections.CollectionBase

{

  protected override void OnInsert( int index, object value )

  {

    try

    {

      int newValue = System.Convert.ToInt32( value );

      Console.WriteLine( "Inserting {0} at position {1}",

        index.ToString(), value.ToString());

        Console.WriteLine( "List Contains {0} items",

        this.List.Count.ToString());

    }

    catch( FormatException e )

    {

      throw new ArgumentException(

       "Argument Type not an integer",

       "value", e );

    }

  }

  protected override void OnInsertComplete( int index,

    object value )

  {

    Console.WriteLine( "Inserted {0} at position {1}",

      index.ToString( ), value.ToString( ));

    Console.WriteLine( "List Contains {0} items",

      this.List.Count.ToString( ) );

  }

}

public class MainProgram

{

  public static void Main()

  {

    IntList l = new IntList();

    IList il = l as IList;

    il.Insert( 0,3 );

    il.Insert( 0, "This is bad" );

  }

}

上述代碼創(chuàng)建了一個整數(shù)數(shù)組鏈表,并使用IList接口指針往集合中添加兩個不同的值。通過重寫OnInsert()方法,IntList類可以測試插入值的類型,如果其類型不是整數(shù),它就會拋出一個異常。基類為我們提供了默認(rèn)的實現(xiàn),并設(shè)置了一些掛鉤(hook)供我們定制派生類的行為。

CollectionBase基類為我們提供了一個可用的實現(xiàn)。我們基本上不需要編寫很多代碼,因為可以使用基類中提供的通用實現(xiàn)。但是IntList的公有API來自于CollectionBase實現(xiàn)的接口:IList、ICollection和IEnumerable。CollectionBase為我們提供了這些接口的通用實現(xiàn)。

下面談?wù)剬⒔涌谟米鰠?shù)和返回值的情況。一個接口可以被任意數(shù)量的無關(guān)類型實現(xiàn)。針對接口的編碼方式(coding to interface)為其他開發(fā)人員提供了比針對基類型的編碼方式(coding to base class type)更大的靈活性。這很重要,因為.NET環(huán)境將類型繼承層次限定為單繼承。

下面兩個方法執(zhí)行的是同樣的任務(wù):

public void PrintCollection( IEnumerable collection )

{

  foreach( object o in collection )

  Console.WriteLine( "Collection contains {0}",

    o.ToString( ) );

}

public void PrintCollection( CollectionBase collection )

{

  foreach( object o in collection )

  Console.WriteLine( "Collection contains {0}",

    o.ToString( ) );

}

第2個方法的可重用性比較差,它不能和Arrays、ArrayLists、DataTables、Hashtables、ImageLists或其他很多集合類一起使用。將接口作為方法的參數(shù)類型不僅適應(yīng)面廣,而且易于重用。

使用接口為一個類定義API還會為我們提供更大的靈活性。例如,許多應(yīng)用程序都使用DataSet在應(yīng)用程序的組件之間傳遞數(shù)據(jù)。這樣,就很容易將代碼像如下一樣寫死:

public DataSet TheCollection

{

  get { return _dataSetCollection; }

}

這會使我們很容易在將來遇到問題。比如,在未來的某個時候,我們可能不希望向外界提供DataSet,轉(zhuǎn)而提供DataTable或者DataView,甚至是創(chuàng)建自定義的對象。所有這些改變都會破壞現(xiàn)有的代碼。當(dāng)然,我們可以改變參數(shù)類型,但是那會改變類型的公有接口。改變一個類的公有接口,會導(dǎo)致我們對龐大的系統(tǒng)做很多改變。該公有屬性被訪問的所有地方,都需要進(jìn)行改變。

第2個問題更為直接和棘手:DataSet類提供有許多方法可以改變其中包含的數(shù)據(jù)。這樣,類型用戶便可能刪除其中的表,修改其中的列,甚至替換其中的每一個對象。那肯定不會是我們想要的結(jié)果。幸運的是,我們可以通過返回期望給用戶使用的接口(而非返回整個DataSet對象引用),來限制類型用戶的能力。DataSet支持IListSource接口,可作數(shù)據(jù)綁定之用:

using System.ComponentModel;

public IListSource TheCollection

{

  get { return _dataSetCollection as IListSource; }

}

IListSource接口允許用戶通過GetList()方法來查看其中的數(shù)據(jù)。它還有一個ContainsListCollection屬性允許用戶判斷集合的整體結(jié)構(gòu)。使用IListSource接口,可以訪問DataSet中的單個條目,但是其整體結(jié)構(gòu)不能被改變。另外,調(diào)用者也不能通過刪除約束或者添加功能,來使用DataSet上的方法改變其中數(shù)據(jù)上可用的行為。

當(dāng)使用類將屬性提供給外界時,它實際上會把整個類的接口暴露給外界。通過使用接口,我們可以選擇只提供那些期望給用戶使用的方法和屬性。用來實現(xiàn)接口的類屬于實現(xiàn)細(xì)節(jié),它會隨著時間的推移而改變(參見條款23)。

此外,不相關(guān)的類型可以實現(xiàn)同樣的接口。假設(shè)我們編寫了一個應(yīng)用程序來管理員工、客戶和廠商。至少在類層次中,它們之間沒有關(guān)聯(lián)。但是,它們共享著某種相同的功能。它們都有名稱,我們可能會在一些Windows控件中顯示這些名稱。

public class Employee

{

  public string Name

  {

    get

    {

      return string.Format( "{0}, {1}", _last, _first );

    }

  }

  // 忽略其他細(xì)節(jié)。

}

public class Customer

{

  public string Name

  {

    get

    {

      return _customerName;

    }

  }

  // 忽略其他細(xì)節(jié)。

}

public class Vendor

{

  public string Name

  {

    get

    {

      return _vendorName;

    }

  }

}

Employee、Customer和Vendor三個類不應(yīng)該共享一個基類。但是它們共享著一些屬性:名稱(如上面的代碼所展示)、地址和聯(lián)系電話。我們可以將這些屬性放在一個接口中:

public interface IContactInfo

{

  string Name { get; }

  PhoneNumber PrimaryContact { get; }

  PhoneNumber Fax { get; }

  Address PrimaryAddress { get; }

}

public class Employee : IContactInfo

{

  // 忽略實現(xiàn)。

}

這個新的接口可以簡化我們的編程任務(wù),因為它允許我們創(chuàng)建相同的函數(shù)來操作不相關(guān)的類型:

public void PrintMailingLabel( IContactInfo ic )

{

  // 忽略實現(xiàn)。

}

上面的函數(shù)可以應(yīng)用于所有實現(xiàn)了IContactInfo接口的類型。Employee、Customer和Vendor類型都可以作為上述函數(shù)的參數(shù),因為它們都實現(xiàn)了該接口。

有時候,使用接口還可以幫助我們避免結(jié)構(gòu)類型的拆箱(unbox)代價。當(dāng)我們將結(jié)構(gòu)實例放入一個裝箱對象時,該裝箱對象實際上支持結(jié)構(gòu)支持的所有接口。當(dāng)通過接口指針來訪問該結(jié)構(gòu)時,我們不必拆箱即可訪問到內(nèi)部的數(shù)據(jù)。下面的例子展示了一個結(jié)構(gòu),其中定義了一個鏈接和一個描述:

public struct URLInfo : IComparable

{

  private string URL;

  private string description;

  public int CompareTo( object o )

  {

    if (o is URLInfo)

    {

      URLInfo other = ( URLInfo ) o;

      return CompareTo( other );

    }

    else

      throw new ArgumentException(

        "Compared object is not URLInfo" );

  }

  public int CompareTo( URLInfo other )

  {

    return URL.CompareTo( other.URL );

  }

}

由于URLInfo實現(xiàn)了IComparable接口,因此我們可以創(chuàng)建一個URLInfo對象的排序鏈表。將URLInfo結(jié)構(gòu)添加到鏈表中時,它會被裝箱。但是Sort()方法不需要對排序過程中需要比較的兩個對象進(jìn)行拆箱,即可調(diào)用CompareTo()方法。當(dāng)然,我們?nèi)匀恍枰獙ζ渲凶鳛閰?shù)的那個對象(other)進(jìn)行拆箱,但是對于調(diào)用IComparable.CompareTo()方法時左邊的那個對象,則不需要拆箱。

綜上所述,基類描述并實現(xiàn)了一組相關(guān)類型間共用的行為。接口則描述了一組比較緊湊的功能,供其他不相關(guān)的具體類型來實現(xiàn)。二者都有自己的用武之地。類定義了我們要創(chuàng)建的類型。接口以功能分組的形式描述了那些類型的行為。如果理解好二者之間的差別,我們便可以創(chuàng)建更富表現(xiàn)力、更能應(yīng)對變化的設(shè)計。應(yīng)該使用類層次來定義相關(guān)的類型,然后讓它們實現(xiàn)不同的接口,以便通過接口向外界提供功能。

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

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多