這個話題不僅僅是針對.NET的,在其他物件導向語言的環境中,例如Java,都會有介面和抽象類別,對於究竟是選擇介面還是抽象類別,已經有了太多的討論,包括設計模式中都有了一條設計原則:組合優於繼承,雖然這條原則和我們要討論話題沒有太大聯絡,但是可以看出在這方面如何做出選擇,並沒有一個萬能的解決方案,一般都要見招拆招,具體問題具體分析。
抽象類別一般是作為一個類階層的頂端,它一般有兩個用途:1)定義邏輯骨架;2)抽取共通方法。它為一組相關的類型提供了一個公用的抽象;而介面是一種按合約進行設計的方式,一個實現了介面的類型,必須要實現介面中定義的方法。
繼承一種“is a”的關係,而介面實現則是一種“behave as”的關係,抽象類別描述了對象是什麼,而介面描述了對象的行為方式。
在介面中,不能包含實現,也不能包含任何具體的資料成員,介面是在聲明一種合約,所有實現介面的類型都要負責履行其中的約定。
抽象類別可以為衍生類別型提供一些具體的實現,我們可以指定資料成員、具體的方法、虛方法的實現、屬性、事件以及索引器。它還可以實現一些具體的方法,因此可以為子類提供一些共通的可重用代碼。抽象類別可以為任何具體的行為提供一個實現,在這方面,介面是不可以的。
在抽象基類和介面之間做選擇,實際上是一個如何隨著時間的推移而更好的支援抽象的問題。介面的特點是比較穩定:我們將一組功能封裝在介面中,作為其他類型的實現合約;而基類則可以隨著時間的推移進行擴充,這些擴充會成為每一個子類的一部分。
C#中,我們可以混合使用抽象類別和介面,可以繼承一個類,然後實現多個介面。
物件導向中的一個設計原則是面向介面編程,因此無論對於介面,還是抽象類別,在傳遞參數的過程中,使用抽象類別型或者介面類型,會比直接使用衍生類別型或者實現介面的類型要好,在擴充的空間。
當我們在傳遞參數的過程中,使用類作為傳遞方式,那麼我們實際上是將整個類的介面暴露給外界,通過使用介面,我們可以選擇只提供哪些期望給使用者的方法和屬性,用來實現介面的類屬於實現細節,它會隨著時間的推移而發生改變。(關於這一點,我個人感覺主要是由於.NET中單繼承、多介面的機製造成的,如果是單繼承、單介面的機制,那麼即使是在傳值過程中使用介面,也會將介面的方法暴露給外界)。
我們來看下面的代碼。
代碼
1 public interface InterfaceA
2 {
3 bool MethodA();
4 }
5
6 public interface InterfaceB
7 {
8 bool MethodB();
9 }
10
11 public abstract class BaseClass
12 {
13 public abstract void AbstractMethodA();
14 }
15
16 public class DerivedClass : BaseClass, InterfaceA, InterfaceB
17 {
18 public override void AbstractMethodA()
19 {
20 throw new NotImplementedException();
21 }
22
23 #region InterfaceA Members
24
25 public bool MethodA()
26 {
27 throw new NotImplementedException();
28 }
29
30 #endregion
31
32 #region InterfaceB Members
33
34 public bool MethodB()
35 {
36 throw new NotImplementedException();
37 }
38
39 #endregion
40 }
上述代碼定義了兩個介面、一個抽象類別和一個繼承自抽象類別實現了兩個介面的具體類。關於傳值過程中使用類和使用介面的區別,我們可以看以下的代碼。
代碼
1 private static void TestWithInterface(InterfaceA interfaceA)
2 {
3 interfaceA.MethodA();
4 }
5
6 private static void TestWithAbstractClass(BaseClass baseClass)
7 {
8 baseClass.AbstractMethodA();
9 }
10
11 private static void TestWithClass(DerivedClass derivedClass)
12 {
13 derivedClass.MethodA();
14 derivedClass.MethodB();
15 derivedClass.AbstractMethodA();
16 }
我們定義了三個用於測試的方法,這三個方法在傳值時,分別使用介面、抽象類別和具體類,我們可以看到使用介面和抽象類別傳遞參數,都是只能提供者或者抽象類別中定義好的方法,而使用具體類傳遞參數,它既可以提供者中的方法,也可以訪問抽象類別中的方法。
因此,如果我們需要向調用方只公開一部分功能,那麼我們應該使用抽象類別或者介面的方式進行傳值。
總結:基類描述並實現了一組相互關聯類型間共通的行為,介面則描述了一組比較緊密關聯的功能, 供其他不相關的具體類型來實現。這兩者都有自己的適用範圍,類定義了我們要建立的類型,介面則以功能分組的形式描述了那些類型的行為。如果理解好而這之間的差別,我們便可以建立更富表現力、更能應對變化的設計。應該使用類層次來定義相關的類型,然後讓它們實現不同的介面,以便通過介面向外界提供功能。