文章目錄
2007-09-14 13:12作者:Anytao出處:論壇整理責任編輯:方舟
1. 引言
在我之前的一篇post《抽象類別和介面的誰是誰非》中,和同事管偉的討論,得到很多朋友的關注,因為是不成體系的論道,所以給大家瞭解造成不便,同時關於這個主題的系統性理論,我認為也有必要做以總結,因此才有了本篇的新鮮出爐。同時,我將把上貼中的問題順便也在此做以交代。
2. 概念引入
什麼是介面?
介面是包含一組虛方法的抽象類別型,其中每一種方法都有其名稱、參數和傳回值。介面方法不能包含任何實現,CLR允許介面可以包含事件、屬性、索引器、靜態方法、靜態欄位、靜態建構函式以及常數。但是注意:C#中不能包含任何靜態成員。一個類可以實現多個介面,當一個類繼承某個介面時,它不僅要實現該介面定義的所有方法,還要實現該介面從其他介面中繼承的所有方法。
定義方法為:
public interface System.IComparable
{
int CompareTo(object o);
}
public class TestCls: IComparable
{
public TestCls()
{
}
private int _value;
public int Value
{
get { return _value; }
set { _value = value; }
}
public int CompareTo(object o)
{
//使用as模式進行轉型判斷
TestCls aCls = o as TestCls;
if (aCls != null)
{
//實現抽象方法
return _value.CompareTo(aCls._value);
}
}
}
什麼是抽象類別?
抽象類別提供多個衍生類別共用基類的公用定義,它既可以提供抽象方法,也可以提供非抽象方法。抽象類別不能執行個體化,必須通過繼承由衍生類別實現其抽象方法,因此對抽象類別不能使用new關鍵字,也不能被密封。如果衍生類別沒有實現所有的抽象方法,則該衍生類別也必須聲明為抽象類別。另外,實現抽象方法由overriding方法來實現。
定義方法為:
///
/// 定義抽象類別
///
abstract public class Animal
{
//定義靜態欄位
static protected int _id;
//定義屬性
public abstract static int Id
{
get;
set;
}
//定義方法
public abstract void Eat();
//定義索引器
public string this[int i]
{
get;
set;
}
///
/// 實現抽象類別
///
public class Dog: Animal
{
public static override int Id
{
get {return _id;}
set {_id = value;}
}
public override void Eat()
{
Console.Write("Dog Eats.")
}
}
3. 相同點和不同點
3.1 相同點
都不能被直接執行個體化,都可以通過繼承實現其抽象方法。
都是面向抽象編程的技術基礎,實現了諸多的設計模式。
3.2 不同點
介面支援多繼承;抽象類別不能實現多繼承。
介面只能定義抽象規則;抽象類別既可以定義規則,還可能提供已實現的成員。
介面是一組行為規範;抽象類別是一個不完全的類,著重族的概念。
介面可以用於支援回調;抽象類別不能實現回調,因為繼承不支援。
介面只包含方法、屬性、索引器、事件的簽名,但不能定義欄位和包含實現的方法;抽象類別可以定義欄位、屬性、包含有實現的方法。
介面可以作用於實值型別和參考型別;抽象類別只能作用於參考型別。例如,Struct就可以繼承介面,而不能繼承類。
通過相同與不同的比較,我們只能說介面和抽象類別,各有所長,但無優略。在實際的編程實踐中,我們要視具體情況來酌情量才,但是以下的經驗和積累,或許能給大家一些啟示,除了我的一些積累之外,很多都來源於經典,我相信經得起考驗。所以在規則與場合中,我們學習這些經典,最重要的是學以致用,當然我將以一家之言博大家之笑,看官請繼續。
3.3 規則與場合
請記住,物件導向思想的一個最重要的原則就是:面向介面編程。
藉助介面和抽象類別,23個設計模式中的很多思想被巧妙的實現了,我認為其精髓簡單說來就是:面向抽象編程。
抽象類別應主要用於關係密切的對象,而介面最適合為不相關的類提供通用功能。
介面著重於CAN-DO關聯類型,而抽象類別則偏重於IS-A式的關係;
介面多定義對象的行為;抽象類別多定義對象的屬性;
介面定義可以使用public、protected、internal 和private修飾符,但是幾乎所有的介面都定義為public,原因就不必多說了。
“介面不變”,是應該考慮的重要因素。所以,在由介面增加擴充時,應該增加新的介面,而不能更改現有介面。
盡量將介面設計成功能單一的功能塊,以.NET Framework為例,IDisposable、IDisposable、IComparable、IEquatable、IEnumerable等都只包含一個公用方法。
介面名稱前面的大寫字母“I”是一個約定,正如欄位名以底線開頭一樣,請堅持這些原則。
在介面中,所有的方法都預設為public。
如果預計會出現版本問題,可以建立“抽象類別”。例如,建立了狗(Dog)、雞(Chicken)和鴨(Duck),那麼應該考慮抽象出動物(Animal)來應對以後可能出現風馬牛的事情。而向介面中添加新成員則會強制要求修改所有衍生類別,並重新編譯,所以版本式的問題最好以抽象類別來實現。
從抽象類別派生的非抽象類別必須包括繼承的所有抽象方法和抽象訪問器的實實現。
對抽象類別不能使用new關鍵字,也不能被密封,原因是抽象類別不能被執行個體化。
在抽象方法聲明中不能使用 static 或 virtual 修飾符。
以上的規則,我就厚顏無恥的暫訂為T14條吧,寫的這麼累,就當一時的獎賞吧。大家也可以互連有無,我將及時修訂。
4. 經典樣本
4.1 絕對經典
.NET Framework是學習的最好資源,有意識的研究FCL是每個.NET程式員的必修課,關於介面和抽象類別在FCL中的使用,我有以下的建議:
FCL對集合類使用了基於介面的設計,所以請關注System.Collections中關於介面的設計實現;
FCL對資料流相關類使用了基於抽象類別的設計,所以請關注System.IO.Stream類的抽象類別設計機制。
4.2 別樣小菜
下面的執行個體,因為是我的理解,因此給經典打上“相對”的記號,至於什麼時候晉陞為“絕對”,就看我在.NET追求的路上,是否能夠一如既往的如此執著,因此我將把相對重構到絕對為止(呵呵)。 本樣本沒有闡述抽象類別和介面在設計模式中的應用,因為那將是另一篇有討論價值的文本,本文著眼與概念和原則的把握,但是真正的應用來自於具體的需求規範。
設計結構:
1. 定義抽象類別
public abstract class Animal
{
protected string _name;
//聲明抽象屬性
public abstract string Name
{
get;
}
//聲明抽象方法
public abstract void Show();
//實現一般方法
public void MakeVoice()
{
Console.WriteLine("All animals can make voice!");
}
}
2. 定義介面
public interface IAction
{
//定義公用方法標籤
void Move();
}
3. 實現抽象類別和介面
public class Duck : Animal, IAction
{
public Duck(string name)
{
_name = name;
}
//重載抽象方法
public override void Show()
{
Console.WriteLine(_name + " is showing for you.");
}
//重載抽象屬性
public override string Name
{
get { return _name;}
}
//實現介面方法
public void Move()
{
Console.WriteLine("Duck also can swim.");
}
}
public class Dog : Animal, IAction
{
public Dog(string name)
{
_name = name;
}
public override void Show()
{
Console.WriteLine(_name + " is showing for you.");
}
public override string Name
{
get { return _name; }
}
public void Move()
{
Console.WriteLine(_name + " also can run.");
}
}
4. 用戶端實現
public class TestAnmial
{
public static void Main(string [] args)
{
Animal duck = new Duck("Duck");
duck.MakeVoice();
duck.Show();
Animal dog = new Dog("Dog");
dog.MakeVoice();
dog.Show();
IAction dogAction = new Dog("A big dog");
dogAction.Move();
}
}
5. 他山之石
正所謂真理是大家看出來的,所以將園子裡有創新性的觀點潛列於此,一是感謝大家的共用,二是完善一家之言的不足,希望能夠將領域形成知識,受用於我,受用於眾。
dunai認為:抽象類別是提取具體類的公因式,而介面是為了將一些不相關的類“雜湊”成一個共同的群體。至於他們在各個語言中的句法,語言細節並不是我關心的重點。
樺山澗的收藏也很不錯。
Artech認為:所代碼共用和可擴充性考慮,盡量使用Abstract Class。當然介面在其他方面的優勢,我認為也不可忽視。
shenfx認為:當在差異較大的對象間尋求功能上的共性時,使用介面;當在共性較多的對象間尋求功能上的差異時,使用抽象基類。
最後,MSDN的建議是:
如果預計要建立組件的多個版本,則建立抽象類別。抽象類別提供簡單易行的方法來控制組件版本。通過更新基類,所有繼承類都隨更改自動更新。另一方面,介面一旦建立就不能更改。如果需要介面的新版本,必須建立一個全新的介面。
如果建立的功能將在大範圍的全異對象間使用,則使用介面。抽象類別應主要用於關係密切的對象,而介面最適合為不相關的類提供通用功能。
如果要設計小而簡練的功能塊,則使用介面。如果要設計大的功能單元,則使用抽象類別。
如果要在組件的所有實現間提供通用的已實現功能,則使用抽象類別。抽象類別允許部分實作類別,而介面不包含任何成員的實現。
6. 結論
介面和抽象類別,是論壇上、課堂間討論最多的話題之一,之所以將這個老話題拿出來再議,是因為從我的體會來說,深刻的理解這兩個物件導向的基本內容,對於盤活物件導向的抽象化編程思想至關重要。本文基本概況了介面和抽象類別的概念、異同和使用規則,從學習的觀點來看,我認為這些總結已經足以表達其核心。但是,對於物件導向和軟體設計的深入理解,還是建立在不斷實踐的基礎上,Scott說自己每天堅持一個小時用來寫Demo,那麼我們是不是更應該勤於鍵盤呢。對於介面和抽象類別,請多用而知其然,多想而知其奧吧。