1. 引言 2. 概念引入 ●什么是接口? 接口是包含一組虛方法的抽象類型,其中每一種方法都有其名稱、參數(shù)和返回值。接口方法不能包含任何實現(xiàn),CLR允許接口可以包含事件、屬性、索引 器、靜態(tài)方法、靜態(tài)字段、靜態(tài)構(gòu)造函數(shù)以及常數(shù)。但是注意:C#中不能包含任何靜態(tài)成員。一個類可以實現(xiàn)多個接口,當(dāng)一個類繼承某個接口時,它不僅要實現(xiàn) 該接口定義的所有方法,還要實現(xiàn)該接口從其他接口中繼承的所有方法。 定義方法為: 以下為引用的內(nèi)容: 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模式進行轉(zhuǎn)型判斷 TestCls aCls = o as TestCls; if (aCls != null) { //實現(xiàn)抽象方法 return _value.CompareTo(aCls._value); } } } ●什么是抽象類? 抽象類提供多個派生類共享基類的公共定義,它既可以提供抽象方法,也可以提供非抽象方法。抽象類不能實例化,必須通過繼承由派生類實現(xiàn)其抽象方法, 因此對抽象類不能使用new關(guān)鍵字,也不能被密封。如果派生類沒有實現(xiàn)所有的抽象方法,則該派生類也必須聲明為抽象類。另外,實現(xiàn)抽象方法由 overriding方法來實現(xiàn)。 定義方法為: 以下為引用的內(nèi)容: /// <summary> /// 定義抽象類 /// </summary> abstract public class Animal { //定義靜態(tài)字段 static protected int _id; //定義屬性 public abstract static int Id { get; set; } //定義方法 public abstract void Eat();
//定義索引器 public string this[int i] { get; set; } } /// <summary> /// 實現(xiàn)抽象類 /// </summary> 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 相同點 ●都不能被直接實例化,都可以通過繼承實現(xiàn)其抽象方法。 ●都是面向抽象編程的技術(shù)基礎(chǔ),實現(xiàn)了諸多的設(shè)計模式。 3.2 不同點 ●接口支持多繼承;抽象類不能實現(xiàn)多繼承。 ●接口只能定義抽象規(guī)則;抽象類既可以定義規(guī)則,還可能提供已實現(xiàn)的成員。 ●接口是一組行為規(guī)范;抽象類是一個不完全的類,著重族的概念。 ●接口可以用于支持回調(diào);抽象類不能實現(xiàn)回調(diào),因為繼承不支持。 ●接口只包含方法、屬性、索引器、事件的簽名,但不能定義字段和包含實現(xiàn)的方法;抽象類可以定義字段、屬性、包含有實現(xiàn)的方法。 ●接口可以作用于值類型和引用類型;抽象類只能作用于引用類型。例如,Struct就可以繼承接口,而不能繼承類。 通過相同與不同的比較,我們只能說接口和抽象類,各有所長,但無優(yōu)略。在實際的編程實踐中,我們要視具體情況來酌情量才,但是以下的經(jīng)驗和積累,或 許能給大家一些啟示,除了我的一些積累之外,很多都來源于經(jīng)典,我相信經(jīng)得起考驗。所以在規(guī)則與場合中,我們學(xué)習(xí)這些經(jīng)典,最重要的是學(xué)以致用,當(dāng)然我將 以一家之言博大家之笑,看官請繼續(xù)。 3.3 規(guī)則與場合 1.請記住,面向?qū)ο笏枷氲囊粋€最重要的原則就是:面向接口編程。 2.借助接口和抽象類,23個設(shè)計模式中的很多思想被巧妙的實現(xiàn)了,我認為其精髓簡單說來就是:面向抽象編程。 3.抽象類應(yīng)主要用于關(guān)系密切的對象,而接口最適合為不相關(guān)的類提供通用功能。 4.接口著重于CAN-DO關(guān)系類型,而抽象類則偏重于IS-A式的關(guān)系; 5.接口多定義對象的行為;抽象類多定義對象的屬性; 6.接口定義可以使用public、protected、internal 和private修飾符,但是幾乎所有的接口都定義為public,原因就不必多說了。 7.“接口不變”,是應(yīng)該考慮的重要因素。所以,在由接口增加擴展時,應(yīng)該增加新的接口,而不能更改現(xiàn)有接口。 8.盡量將接口設(shè)計成功能單一的功能塊,以.NET Framework為例,IDisposable、IDisposable、IComparable、IEquatable、IEnumerable等都只包含一個公共方法。 9.接口名稱前面的大寫字母“I”是一個約定,正如字段名以下劃線開頭一樣,請堅持這些原則。 10.在接口中,所有的方法都默認為public。 11. 如果預(yù)計會出現(xiàn)版本問題,可以創(chuàng)建“抽象類”。例如,創(chuàng)建了狗(Dog)、雞(Chicken)和鴨(Duck),那么應(yīng)該考慮抽象出動物 (Animal)來應(yīng)對以后可能出現(xiàn)風(fēng)馬牛的事情。而向接口中添加新成員則會強制要求修改所有派生類,并重新編譯,所以版本式的問題最好以抽象類來實現(xiàn)。 12.從抽象類派生的非抽象類必須包括繼承的所有抽象方法和抽象訪問器的實實現(xiàn)。 13.對抽象類不能使用new關(guān)鍵字,也不能被密封,原因是抽象類不能被實例化。 14.在抽象方法聲明中不能使用 static 或 virtual 修飾符。 以上的規(guī)則,我就厚顏無恥的暫定為T14條吧,寫的這么累,就當(dāng)一時的獎賞吧。大家也可以互通有無,我將及時修訂。 4. 經(jīng)典示例 4.1 絕對經(jīng)典 .NET Framework是學(xué)習(xí)的最好資源,有意識的研究FCL是每個.NET程序員的必修課,關(guān)于接口和抽象類在FCL中的使用,我有以下的建議: 1.FCL對集合類使用了基于接口的設(shè)計,所以請關(guān)注System.Collections中關(guān)于接口的設(shè)計實現(xiàn); 2.FCL對數(shù)據(jù)流相關(guān)類使用了基于抽象類的設(shè)計,所以請關(guān)注System.IO.Stream類的抽象類設(shè)計機制。 4.2 別樣小菜 下面的實例,因為是我的理解,因此給經(jīng)典打上“相對”的記號,至于什么時候晉升為“絕對”,就看我在.NET追求的路上,是否能夠一如既往的如此執(zhí) 著,因此我將把相對重構(gòu)到絕對為止(呵呵)。 本示例沒有闡述抽象類和接口在設(shè)計模式中的應(yīng)用,因為那將是另一篇有討論價值的文本,本文著眼與概念和原則的把握,但是真正的應(yīng)用來自于具體的需求規(guī)范。 設(shè)計結(jié)構(gòu)如圖所示:
1. 定義抽象類 以下為引用的內(nèi)容: public abstract class Animal { protected string _name; //聲明抽象屬性 public abstract string Name { get; } //聲明抽象方法 public abstract void Show(); //實現(xiàn)一般方法 public void MakeVoice() { Console.WriteLine("All animals can make voice!"); } }
2. 定義接口 以下為引用的內(nèi)容: public interface IAction { //定義公共方法標簽 void Move(); } 3. 實現(xiàn)抽象類和接口 以下為引用的內(nèi)容: 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;} } //實現(xiàn)接口方法 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. 客戶端實現(xiàn) 以下為引用的內(nèi)容: 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. 他山之石
正所謂真理是大家看出來的,所以將園子里有創(chuàng)新性的觀點潛列于此,一是感謝大家的共享,二是完善一家之言的不足,希望能夠?qū)㈩I(lǐng)域形成知識,受用于我,受用于眾。 ●[url=]dunai[/url]認為:抽象類是提取具體類的公因式,而接口是為了將一些不相關(guān)的類“雜湊”成一個共同的群體。至于他們在各個語言中的句法,語言細節(jié)并不是我關(guān)心的重點。 ●樺山澗的收藏也很不錯。 ●Artech認為:所代碼共用和可擴展性考慮,盡量使用Abstract Class。當(dāng)然接口在其他方面的優(yōu)勢,我認為也不可忽視。 ●shenfx認為:當(dāng)在差異較大的對象間尋求功能上的共性時,使用接口;當(dāng)在共性較多的對象間尋求功能上的差異時,使用抽象基類。
最后,MSDN的建議是: ●如果預(yù)計要創(chuàng)建組件的多個版本,則創(chuàng)建抽象類。抽象類提供簡單易行的方法來控制組件版本。通過更新基類,所有繼承類都隨更改自動更新。另一方面,接口一旦創(chuàng)建就不能更改。如果需要接口的新版本,必須創(chuàng)建一個全新的接口。 ●如果創(chuàng)建的功能將在大范圍的全異對象間使用,則使用接口。抽象類應(yīng)主要用于關(guān)系密切的對象,而接口最適合為不相關(guān)的類提供通用功能。 ●如果要設(shè)計小而簡練的功能塊,則使用接口。如果要設(shè)計大的功能單元,則使用抽象類。 ●如果要在組件的所有實現(xiàn)間提供通用的已實現(xiàn)功能,則使用抽象類。抽象類允許部分實現(xiàn)類,而接口不包含任何成員的實現(xiàn)。 6. 結(jié)論 接口和抽象類,是論壇上、課堂間討論最多的話題之一,之所以將這個老話題拿出來再議,是因為從我的體會來說,深刻的理解這兩個面向?qū)ο蟮幕緝?nèi)容, 對于盤活面向?qū)ο蟮某橄蠡幊趟枷胫陵P(guān)重要。本文基本概況了接口和抽象類的概念、異同和使用規(guī)則,從學(xué)習(xí)的觀點來看,我認為這些總結(jié)已經(jīng)足以表達其核心。 但是,對于面向?qū)ο蠛蛙浖O(shè)計的深入理解,還是建立在不斷實踐的基礎(chǔ)上,Scott說自己每天堅持一個小時用來寫Demo,那么我們是不是更應(yīng)該勤于鍵盤 呢。對于接口和抽象類,請多用而知其然,多想而知其奧吧。
|