介面是一種按照契約設計的方式,一個類型必須實行介面中定義的方法。抽象基類則為一組相關的類型提供了一個共有的抽象。要注意二者的使用情境和區別:基類描述了對象是什麼;介面描述了對象將如何表現行為。
1.關於介面
介面描述了一組功能,是一個契約,任何實現介面的類型必須為介面中定義的所有所有元素提供具體的實現。我們應該將可重用的行為提取出來,定義在介面中;由於不同相關的類型均可以實現一個介面,所有這會增加代碼的重用率。對於開發人員本身來說,實現介面要比繼承自訂的基類更容易。
2.關於抽象基類
抽象基類除了描述共同行為,抽象基類還可以為衍生類別提供一些具體的實現(為子類通過通用、可重用的代碼)。抽象基類可以為任何具體行為提供一個實現,而介面則不能。這種實現重用的方式為我們提供了另一種好處:在擴充系統功能時,通過向基類中添加並實現某種功能,所有的衍生類別立即擁有該功能,而向介面中添加一個成員,則會破壞所有實現該介面的類。
3.使用介面替代繼承
使用抽象基類還是介面,代表了對日後可能發生的變化兩種不同的態度:將一組功能封裝在一個介面中,作為其他類型的實現契約。而基類則可以再日後進行擴充,這些擴充也會自動的成為子類的一部分。
3.1通過擴充方法來類比繼承
其實,這兩種方式可以混合使用,也就是說:既可以讓類型支援多個介面,也讓其可以重用。我們知道,介面不能包含實現,也不能包含任何具體的資料成員。但是,擴充方法卻是可以應用在介面上。例如:System.Linq.Enumerable類中就包含了30多個聲明於IEnumerable<T>介面之上的擴充方法,所有實現了該介面的類型都會自動獲得這些擴充方法自身的實現(注意:介面本身並不能包含任何實現,只是通過擴充方法的形式類比地提供一些實現)。例如,下面的這個針對IEnumerable介面擴充方法:
1 public static class Extensions 2 { 3 /// <summary> 4 /// 為IEnumerable<T>類型添加擴充方法 5 /// </summary> 6 /// <typeparam name="T"></typeparam> 7 /// <param name="sequence"></param> 8 /// <param name="action"></param> 9 public static void ForAll<T>(this IEnumerable<T> sequence, Action<T> action)10 {11 foreach (T item in sequence)12 {13 action(item);14 }15 }16 }
所有實現了IEnumerable介面的類型,會自動擷取這個方法的實現;同樣的,如果一個類實現了IEnumerable介面,那麼它也會獲得該介面的所有擴充方法的實現。
3.2 介面編程的靈活性
在.NET 環境下的繼承是單根繼承,根據介面編程要比根據基類編程擁有更大的靈活性,因為一個類型可以實現多個介面。同樣的不相關的類型也可以實現同一個介面,這對在為不相關類編寫公有邏輯時,使用介面可以簡化你的工作。看下面的樣本:假設某個程式需要管理員工、客戶、第三方員工,這些類型並不相關(從繼承體系來看),但是他們有一些公有屬性,例如:名稱、地址、電話等等:
View Code
1 public class Employee 2 { 3 public string FirstName{get;set;} 4 public string LastName{get;set;} 5 6 public string LastName 7 { 8 get 9 {10 return string.Format("{0},{1}",LastName,FirstName);11 }12 }13 //略...14 }15 16 public class Customer17 {18 public string LastName19 {20 get21 {22 return CustomerName;23 }24 }25 //略...26 private string CustomerName;27 }28 29 public class Vendor30 {31 public string Name32 {33 get34 {35 return vendorName;36 }37 }38 //...39 private string vendorName;40 }
上面顯示姓名屬性,其他屬性略過。現在我們可以把公用屬性抽象成一個介面:
public interface IContactInfo{ //姓名 string Name{get;} //聯絡電話 PhoneNumber primaryContact{get;} //傳真 PhoneNumber Fax{get;} //住址 Address primaryAddress{get;}}
我們將上面所以類都實現IContactInfo介面:
1 //..其他類 略..2 public class Employee:IContactInfo3 {4 //...略5 }
現在我們需要編寫一個為這些類型列印自身資訊的新方法:
1 public void PrintMailingLabel(IContactInfo ic)2 {3 //...略4 }
我們可以看到所有的,只要是實現了該介面的類型都可以使用該方法,這得益於將我們公有邏輯放在了介面中。
同時,使用介面定義類的API可會提供更好的靈活性,當類型的屬性以類的形式暴露時,也就暴露了該類的所有介面。而若是以暴露某個介面,那麼就可以選擇僅為使用者提供那些必要的方法和屬性。而實現該介面的類屬性實現細節,可能會隨著時間改變。
3.3 在結構中使用介面避免拆箱
介面和抽象基類還有一個不同之處在於,抽象基類僅限於參考型別,而介面則沒有這個限制。當我們將struct裝箱時,該裝箱對象實際上支援struct支援的所有介面。當通過介面指標來訪問該struct時,我們不必拆箱即可訪問到內部資料。如下面的例子:
1 public struct URLInfo : IComparable<URLInfo>, IComparable 2 { 3 private string URL; 4 private string description; 5 6 public int CompareTo(URLInfo other) 7 { 8 return URL.CompareTo(other.URL); 9 }10 11 #region IComparable 成員12 13 int IComparable.CompareTo(object obj)14 {15 if (obj is URLInfo)16 {17 URLInfo other = (URLInfo)obj;18 return CompareTo(other);19 }20 else21 {22 throw new ArgumentException("比較的對象不是URLInfo類型");23 }24 }25 26 #endregion27 }
由於URLInfo實現了IComparable<T>和IComparable介面,所有我們可以輕鬆的建立一個保護URLInfo對象的排序鏈表。即使在那些依賴老版本的IComparable的代碼也會減少裝箱和拆箱的次數,因為客戶代碼可以再不拆箱的情況下直接調用IComparable.CompareTo()。
小節
介面是一種按契約設計的方式:一個實現了某個介面的類型,必須提供介面中約定的所有方法實現。抽象基類則為一組項目類型提供了一個共用的共同抽象。仔細理解二者之間的差別,使用我們能夠建立更富表現力和提高應對變化的設計。使用類層次來定義相關的類型,用介面暴露功能,並可以讓不同類型實現這些介面。