C#中接口和抽象類的區別

相同點:

  • 1、都可以被繼承
  • 2、都不能被實例化
  • 3、都可以包含方法聲明
  • 4、派生類必須實現未實現的方法

區別:

  • 1、抽象基類可以定義字段、屬性、方法實現。接口只能定義屬性、索引器、事件、和方法聲明,不能包含字段。
  • 2、抽象類是一個不完整的類,需要進一步細化,而接口是一個行爲規範。微軟的自定義接口總是後帶able字段,證明其是表述一類“我能做。。。”
  • 3、接口可以被多重實現,抽象類只能被單一繼承
  • 4、抽象類更多的是定義在一系列緊密相關的類間,而接口大多數是關係疏鬆但都實現某一功能的類中
  • 5、抽象類是從一系列相關對象中抽象出來的概念,因此反映的是事物的內部共性;接口是爲了滿足外部調用而定義的一個功能約定, 因此反映的是事物的外部特性
  • 6、接口基本上不具備繼承的任何具體特點,它僅僅承諾了能夠調用的方法
  • 7、接口可以用於支持回調,而繼承並不具備這個特點
  • 8、抽象類實現的具體方法默認爲虛的,但實現接口的類中的接口方法卻默認爲非虛的,當然您也可以聲明爲虛的
  • 9、如果抽象類實現接口,則可以把接口中方法映射到抽象類中作爲抽象方法而不必實現,而在抽象類的子類中實現接口中方法

使用規則:

  • 1、抽象類主要用於關係密切的對象,而接口最適合爲不相關的類提供通用功能
  • 2、如果要設計大的功能單元,則使用抽象類;如果要設計小而簡練的功能塊,則使用接口。
  • 3、如果預計要創建組件的多個版本,則創建抽象類。接口一旦創建就不能更改。如果需要接口的新版本,必須創建一個全新的接口。
  • 4、如果創建的功能將在大範圍的全異對象間使用,則使用接口;如果要在組件的所有實現間提供通用的已實現功能,則使用抽象類。
  • 5、分析對象,提煉內部共性形成抽象類,用以表示對象本質,即“是什麼”。爲外部提供調用或功能需要擴充時優先使用接口
  • 6、好的接口定義應該是具有專一功能性的,而不是多功能的,否則造成接口污染。如果一個類只是實現了這個接口的中一個功能,而不得不去實現接口中的其他方法,就叫接口污染
  • 7、儘量避免使用繼承來實現組建功能,而是使用黑箱複用,即對象組合。因爲繼承的層次增多,造成最直接的後果就是當你調用這個類羣中某一類,就必須把他們全部加載到棧中!後果可想而知。(結合堆棧原理理解)。同時,有心的朋友可以留意到微軟在構建一個類時,很多時候用到了對象組合的方法。比如 asp.net中,Page類,有Server Request等屬性,但其實他們都是某個類的對象。使用Page類的這個對象來調用另外的類的方法和屬性,這個是非常基本的一個設計原則

例如:

Window窗體可以用抽象類來設計,可以把公有操作和屬性放到一個抽象類裏,讓窗體和對話框繼承自這個抽象類,再根據自己的需求進行擴展和完善。

打印操作可以作爲一個接口提供給每個需要此功能的窗體,因爲窗體的內容不同,就要根據他們自己的要求去實現自己的打印功能。打印時只通過接口來調用,而不用在乎是那個窗體要打印。

共性、個性與選擇:

有的書上寫到C#推薦使用接口(Interface)來替代抽象基類(Abstract Class),並強調使用接口的諸多好處,這點我不敢苟同,從上面列表中看來,兩者之間還是存在不少差異的,而這種差異的存在性必然決定了適用場景的不同,例如在抽象基類中可以爲部分方法提供默認的實現,從而避免在子類中重複實現它們,提高代碼的可重用性,這是抽象類的優勢所在;而接口中只能包含抽象方法。至於何時使用抽象基類何時使用接口關鍵還是取決於用戶是如何看待繼承類之間的聯繫的,用戶更加關心的是它們之間的個性差異還是它們之間的共性聯繫。舉個生活中的例子加以說明。

如果給你三個對象分別是人、魚、青蛙,讓你爲他們設計個基類來概括它們之間的聯繫,那麼首先給你的感覺肯定是它們個體間的差異性較大,很難抽象出共性,然而若讓你概括他們行爲之間的共性,你可能想了想會意識到他們都會游泳,只不過是游泳方式迥異。那麼這時你就應當考慮使用接口而不是抽象基類,原因有三條:

複製代碼
 1 interface ISwim
 2         {
 3             void Swim();
 4         }
 5 
 6         public class Person : ISwim
 7         {
 8             public void Swim()
 9             {
10                 //Swimming in person's style. 
11             }
12         }
13 
14         public class Frog : ISwim
15         {
16             public void Swim()
17             {
18                 //Swimming in frog's style. 
19             }
20         }
21 
22         public class Fish : ISwim
23         {
24             public void Swim()
25             {
26                 //Swimming in fish's style. 
27             }
28         }
複製代碼
  • 1、個性大於共性。
  • 2、差異較大的個性間具有某些相同的行爲。
  • 3、相同行爲的實現方式有較大區別。

這時再給你三個對象,分別是鯽魚、鯉魚、金魚,仍然讓你設計基類來概括它們之間的聯繫,那麼你第一個意識到的肯定是它們都屬於魚類,其次是他們游泳的方式可能稍有差異,這時就應當使用抽象基類而不是接口,對比着上面的例子,原因也有三條:

複製代碼
 1 abstract public class Fish 
 2 { 
 3     abstract public void Swim(); 
 4 }
 5  
 6 public class 鯽魚 : Fish 
 7 { 
 8     public override void Swim() 
 9     { 
10         //Swim like a 鯽魚 
11     } 
12 }
13  
14 public class 鯉魚 : Fish 
15 { 
16     public override void Swim() 
17     { 
18         //Swim like a 鯉魚 
19     } 
20 }
21  
22 public class 金魚 : Fish 
23 { 
24     public override void Swim() 
25     { 
26         //Swim like a 金魚 
27     } 
28 }
複製代碼
  • 1、共性大於個性
  • 2、共性相同的個體間必然具有相同的屬性與行爲
  • 3、相同行爲的實現方式具有一定區別

觀察在使用接口或是使用抽象基類的幾條理由中,第三條理由其實是一樣的,它所描述的是面向對象中多態的概念,即通過覆蓋父類的方法來實現,在運行時根據傳遞的對象引用,來調用相應的方法。第二條理由開始產生分歧,接口更加強調了繼承對象間具有相同的行爲,而抽象類同時還強調了繼承對象間具有相同的屬性。而真正將接口與抽象基類區分開的則是理由歸納如下:

  • 當在差異較大的對象間尋求功能上的共性時,使用接口。
  • 當在共性較多的對象間尋求功能上的差異時,使用抽象基類。

通過相同與不同的比較,我們只能說接口和抽象類,各有所長,但無優略。在實際的編程實踐中,我們要視具體情況來酌情量才,但是以下的經驗和積累,或許能給大家一些啓示,除了我的一些積累之外,很多都來源於經典,我相信經得起考驗。所以在規則與場合中,我們學習這些經典,最重要的是學以致用,當然我將以一家之言博大家之笑,看官請繼續。

規則與場合:

  • 1、請記住,面向對象思想的一個最重要的原則就是:面向接口編程。
  • 2、藉助接口和抽象類,23個設計模式中的很多思想被巧妙的實現了,我認爲其精髓簡單說來就是:面向抽象編程。
  • 3、抽象類應主要用於關係密切的對象,而接口最適合爲不相關的類提供通用功能。
  • 4、接口着重於CAN-DO關係類型,而抽象類則偏重於IS-A式的關係;
  • 5、接口多定義對象的行爲;抽象類多定義對象的屬性;
  • 6、接口定義可以使用public、protected、internal 和private修飾符,但是幾乎所有的接口都定義爲public,原因就不必多說了。
  • 7、“接口不變”,是應該考慮的重要因素。所以,在由接口增加擴展時,應該增加新的接口,而不能更改現有接口。
  • 8、儘量將接口設計成功能單一的功能塊,以.NET Framework爲例,IDisposable、IDisposable、IComparable、IEquatable、IEnumerable等都只包含一個公共方法。
  • 9、接口名稱前面的大寫字母“I”是一個約定,正如字段名以下劃線開頭一樣,請堅持這些原則。
  • 10、在接口中,所有的方法都默認爲public。
  • 11、如果預計會出現版本問題,可以創建“抽象類”。例如,創建了狗(Dog)、雞(Chicken)和鴨(Duck),那麼應該考慮抽象出動物(Animal)來應對以後可能出現風馬牛的事情。而向接口中添加新成員則會強制要求修改所有派生類,並重新編譯,所以版本式的問題最好以抽象類來實現。
  • 12、從抽象類派生的非抽象類必須包括繼承的所有抽象方法和抽象訪問器的實實現。
  • 13、對抽象類不能使用new關鍵字,也不能被密封,原因是抽象類不能被實例化。
  • 14、在抽象方法聲明中不能使用 static 或 virtual 修飾符。

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/fxh_hua/archive/2009/08/20/4464739.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章