從代碼的語法定義和使用邏輯兩個方面淺談接口與抽象類的區別.網絡
1 語法定義篇dom
(1)首先是定義語法ide
接口this
接口的定義是spa
[訪問修飾符] interface 接口名設計
{3d
// 接口成員code
}對象
抽象類blog
抽象類的定義是
[訪問修飾符] abstract class 類名
{
// 類成員
}
定義語法中接口關鍵字interface,類關鍵字class沒什麼差別,抽象類多一個abstract修飾
(2) 成員類型
接口
對於接口,它是定義的一類能力,所以以功能爲主,面向一類抽象能力,因此成員只與方法有關. 那麼記憶接口能定義什麼,就記住方法即可.
舉個例子,對於接口成員能夠包含:方法、屬性、事件和索引器.
實際上能夠知道,屬性是特殊的方法,事件是一個私有的委託變量加上兩個方法,而索引實際上就是屬性,所以對於接口成員的記憶,能夠歸結爲「方法」
抽象類
抽象類的成員與通常類成員沒什麼區別,只不過抽象成員必須在抽象類中. 而在抽象類中通常定義的抽象成員有方法、屬性、事件和索引器.
另外,抽象類能夠有構造方法,其做用是爲非抽象成員進行初始化,而不是做爲建立抽象類的實例而使用
注:即便抽象構造方法不能被外界調用,可是也不能設定爲private,由於其派生類會默認調用無參構造方法
(3)成員訪問修飾
接口
接口就是爲了被實現的,即每一個成員都是要被別的類進行實現的,那麼每一個成員都應該是public類型,所以不用設定接口中成員類型的訪問修飾符,默認爲public,通常語法爲
返回類型 方法名(參數列表);
抽象類
抽象成員必須設定爲public,其他沒有要求
(4)各抽象成員的定義
接口
方法的定義:
返回類型 方法名(參數列表);
屬性的定義:
返回類型 屬性名{ get; set; }
事件的定義:
event 委託名 事件名;
索引的定義:
返回類型 this[索引類型] { get; set; }
例如:
public interface IMyInterface { void Func(); string Value { get; set; } event EventHandler MyEventHandler; string this[int index] { get; set; } }
注:索引器與屬性的get與set不一樣於自動屬性,是能夠選擇只讀、只寫、仍是讀寫的 通常接口都已I命名開始
抽象類
其抽象成員定義相似,可是通常在抽象類中的抽象成員以方法和屬性居多.
首先抽象成員能夠來自接口得到,只須要在每個成員的前面加上public abstract便可
例如:
public abstract class MyAbs : IMyInterface { public abstract void Func(); public abstract string Value { get; set; } public abstract event EventHandler MyEventHandler; public abstract string this[int index] { get; set; } }
其次,抽象類能夠來自與某個基類,將基類的虛方法用abstract進行修飾,例如
public class BaseClass { public virtual void Func() { // 方法體 } } public abstract class MyAbs : BaseClass { public abstract void Func(); }
注:
一、方法沒有方法體是指參數括號寫完後沒有花括號,直接分號結束;若是寫成
public abstract void Func() { }
不叫做無方法體,只能稱做空實現,這樣會出現語法錯誤.
二、抽象成員必須是public的,同時由abstract修飾.
三、接口中的事件定義與抽象類中的事件定義意義和方法定義相同:
-> 在接口中定義一個事件,至關於同時定義了add和remove沒有方法體的方法
void add_MyEventHandler(EventHandler value);
void remove_EventHandler(EventHandler value);
-> 在抽象類中,至關於兩個abstract方法
public abstact void add_MyEventHandler(EventHandler value);
public abstact void remove_EventHandler(EventHandler value);
三、在其子類中能夠被實現,對於接口實現add和remove方法,對於抽象類重寫它們
(5)抽象成員的實現
實現接口
實現接口的類分三種,一是抽象類,二是通常的類,再就是結構. 而實現方式又分爲實現接口和顯式實現接口.
抽象類
抽象類實現接口,只須要將接口中的成員與方法添加好就好了,可是必須保證成員的訪問級別爲public
例如:
public abstract class MyAbs : IMyInterface { public void Func() { // 實現代碼 } public string Value { get; set; } public event EventHandler MyEventHandler; public string this[int index] { get { // 實現代碼 } set { // 實現代碼 } } }
注意:
一、此處必定不容許丟掉public,由於接口就是用來定義方法,最後是要被訪問的,若是不設爲public,那麼會出現問題.
二、此處的屬性與接口中的屬性意義是不一樣的,此處爲自動屬性,在代碼的後臺編譯器會自動生成一個字段,並填補get和set讀取器.
三、此處的事件也與接口中的事件意義不一樣(從代碼角度看幾乎同樣),這裏的事件,編譯器會自動生成一個同名的私有委託,並將add方法與remove方法補全.
若是不但願實現接口中的方法,能夠將接口成員直接copy下來,在前面加上abstract關鍵字便可. 例如
public abstract class MyAbs : IMyInterface { public abstract void Func(); public abstract string Value { get; set; } public abstract event EventHandler MyEventHandler; public abstract string this[int index] { get; set; } }
那麼此時的各個成員,其本質與接口中的成員同樣,只有方法簽名,沒有實現體.
對於「顯式實現接口」,其實現方式就是將成員寫成
接口名.成員名
的形式. 顯式實現接口與通常的實現區別在於顯式實現的成員只有接口對象才能調用.
實現方式代碼以下
public abstract class MyAbs : IMyInterface { void IMyInterface.Func() { // 方法體 } string IMyInterface.Value { get; set; } event EventHandler IMyInterface.MyEventHandler { add { // 實現代碼 } remove { // 實現代碼 } } string IMyInterface.this[int index] { get { // 實現代碼 } set
{ // 實現代碼 } } }
注意:
一、這裏顯示實現的接口,不容許有訪問修飾符. 由於顯式實現的接口只能由接口對象來調用,這裏不管寫什麼都會出現問題.
二、這裏事件不容許使用默認系統自定義的方式,須要本身添加add方法和remove方法
三、另外顯示實現接口的成員不容許使用abstract進行修飾.
普通類實現接口
對於普通的類實現接口,只須要將接口的成員的代碼實現補全便可,這裏與抽象類是同樣的,一樣對於事件,能夠手動添加add方法和remove方法.
若是在實現的方法中,有部分須要在子類中被重寫,那麼只需在方法前加上virtual進行修飾便可,示例代碼以下:
public class MyClass : IMyInterface { public virtual void Func() { // 方法體 } }
注:
一、這裏的成員訪問修飾符也必須是public.
二、這裏一樣能夠「顯式實現接口」,規則與方法與抽象類中介紹的一致
-> 不容許使用virtual修飾成員
-> 事件必須使用「事件訪問器」語法
-> 成員沒有訪問修飾符
實現接口的除了類之外,接口也能夠實現接口
實現抽象類
抽象類的實現與接口實現由點相似,不過這裏不存在顯式與非顯式罷了. 在實現抽象類的過程當中,抽象類的每個抽象成員都須要提供實現代碼,由於抽象類中包含沒抽象成員,所以繼承自抽象類的類,只用提供重寫代碼便可.
不過在語法中與接口的實現仍是有點不同. 接口中成員的實現與通常的寫法是同樣的,可是實現抽象類的成員,每個成員前面都要有override的修飾. 代碼以下:
public class MyClass : MyAbs { public override void Func() { // 實現代碼 } public override string Value { get; set; } public override event EventHandler MyEventHandler; public override string this[int index] { set { // 實現代碼 } get { // 實現代碼 } } }
注:
一、這裏實現的成員必須使用public override進行修飾.
二、事件能夠不本身提供add方法與remove方法
-> 此時編譯器會自動添加一個私有的委託變量,以及add方法與remove方法
三、這裏必須保證每個成員都要從新實現,這裏示例代碼中的屬性是自定屬性,所以看起來與抽象類中的同樣,可是編譯器會本身添加私有字段與get方法和get方法.
四、若是依舊不須要某些方法提供方法體,即仍舊是抽象成員,那麼須要補全其餘方法,將不須要方法提的方法用abstract修飾便可,同時不要忘記類也應該使用abstract進行修飾,由於抽象成員只能容許在抽象類中存在.
屬性的實現
屬性定義爲抽象類型能夠由三種形式:只讀屬性、只寫屬性和讀寫屬性.
在實現屬性的時候,前面提到的例子都是讀寫屬性,下面來看看另外兩種.
只讀屬性
只讀屬性的定義爲:
public interface IMyInterface { string Value { get; } } public abstract class MyAbs { public abstract string Value { get; } }
注:經過前面的介紹,能夠知道實現抽象成員,接口與抽象類是相似的,那麼此處介紹以接口的實現爲規範描述.
實現該屬性的有兩種方案,第一種就是提供一個字段,並添加讀取器的實現代碼,例如
public class MyClass : IMyInterface { private string _value; public string Value { get { return this._value; } } }
第二種方案即是補全兩種讀取器,例如
public class MyClass : IMyInterface { private string _value; public string Value { get { return this._value; } set { this._value = value; } } }
注:
一、對於第二種方案,在顯式實現接口的時候不能使用,由於在接口中不存在補全的讀取器.
二、對於添加進來的讀取器可使用訪問修飾符進行修飾,例如protected等.
(6)使用邏輯篇
接口與抽象類都是抽象的數據類型,雖然使用中有不少比較模糊的地方發,也就是說部分狀況接口與抽象類的使用時能夠互換的. 可是詳細來講接口與抽象類仍是有不少不一樣的地方. 總的來講就是注重功能的時候使用接口,注重對象的時候使用抽象類.
對於接口,從定義就能夠發現是對方法的抽象,也可描述成對能力的抽象;而抽象類是對以事物的抽象,與具體的對象有關. 這麼說很混亂,直接上例子看看.
先考慮如下接口代碼:
public interface IMobilePhone { void Call(); void Message(); } public interface IProgram { void RunApp(); }
先對這兩個接口作一個說明. 爲了描述抽象類與接口在使用中的關係,我打算描述如下手機在生活中的狀況,這樣區分接口與抽象類的關係表現會明顯許多.
這裏定義一個接口IMobilePhone,用來表示手機最基本的能力「打電話」和「處理短信」,這裏不考慮較複雜的狀況,好比接電話與撥電話,發短信與收短信等.
定義接口IProgram用來描述智能機可以運行應用程序. 一樣不考慮較複雜狀況.
咱們知道,手機是一個很泛的概念,好比你試想一下——你見過手機嗎?或許說你只能分辨什麼是手機,若是讓你來描述你明顯的會用一個你們熟悉的另外一部手機來比擬,好比「和蘋果同樣能夠怎麼怎麼」,「和諾基亞5800同樣能夠怎麼樣怎麼樣」等. 所以說手機其實是一個抽象的概念,能夠將手機定義爲一個抽象類,以下代碼.
public abstract class MobilePhone : IMobilePhone { public virtual void Call() { Console.WriteLine(「打電話了」); } public virtual void Message() { Console.WriteLine(「處理電信了」); } }
注:
一、此處使用虛方法的緣由是考慮大多數手機的電話處理與短信處理類似,只有一些比較高級的手機處理短信會有彩信、藍牙信息、多媒體信息等,所以等到其餘具體智能機時再考慮重寫.
二、這裏考慮手機的基本功能,爲描述接口與抽象類的一些區別,所以省略其餘無關信息.
那麼此處手機是有了,可是什麼型號?什麼品牌?很明顯手機只是一個概念,具體到手機還有品牌,型號等不少信息,那麼能夠用這個抽象類派生一個諾基亞手機出來. 不過像諾基亞這樣的大廠商有五、6k的手機,也有充100元話費贈送的手機,很顯然,諾基亞手機也是抽象類. 代碼以下:
public abstract class Nokia:MobilePhone { public readonly string Mark = 「Nokia」; public abstract string FaceDesigner { get; } public abstract string Keyboard { get; } }
注:
一、這裏定義的兩個只讀屬性,FaceDesigner表示板式,是翻蓋仍是直板;Keyboard表示鍵盤是虛擬仍是非虛擬.
二、定義一個只讀的字段,表示手機品牌,而後具體的手機由該類來派生.
下面就100元手機和Nokia Lumia 900做個模擬(趕個潮流!哈哈!!!)
100元機型
直接上代碼:
public class Nokia1280 : Nokia { static int index; string numMark; string faceDesigner; string keyboard; public Nokia1280() { numMark = base.Mark + 「 1280 No」 + (++index).ToString(「0000」); faceDesigner = 「直板機」; keyboard = 「物理鍵盤」; } public string NumMark { get { return this.numMark; } } public override string FaceDesigner { get { return this.faceDesigner; } } public override string Keyboard { get { return this.keyboard; } } }
注:
一、由於每臺手機都有一個惟一的序列號,這裏使用自增靜態的index來描述該機型的產量,每生產一部手機,就會自動增長一次. 並將數據與品牌進行字符串處理,賦值給numMark,便可知道這款手機的型號與編號了.
二、添加構造方法,設定爲直板機型與物理鍵盤.
三、實現每個屬性.
Nokia Lumia 900
因爲這裏是絕對的智能機,毫無疑問須要考慮能夠執行應用程序的狀況,所以該類繼承自Nokia同時實現IProgram接口,可是智能機還有5800、N8等別的型號啊,因此很明顯,智能機須要有一個類來進行描述,那即是一個智能機的抽象類. 代碼以下
public abstract class NokiaApp : Nokia, IProgram { public abstract void RunApp(); }
而後再派生出Lumia900
public class Nokia_Lumia_900 : NokiaApp { static int index; string numMark; string faceDesigner; string keyboard; public Nokia_Lumia_900() { numMark = base.Mark + 「 Lumia 900 No」 + (++index).ToString(「0000」); faceDesigner = 「直板機」; keyboard = 「虛擬鍵盤」; } public override void Call() { Console.WriteLine(「用藍牙打電話了」); } public override void Message() { Console.WriteLine(「處理短信、彩信、多媒體信息」); } public override void RunApp() { Console.WriteLine(「運行Windows Phone程序了」); } public string NumMark { get { return this.numMark; } } public override string FaceDesigner { get { return this.faceDesigner; } } public override string Keyboard { get { return this.keyboard; } } }
注:
一、智能手機實現IProgram接口,所以抽象類NokiaApp繼承自Nokia,實現IProgram.
二、廠商可能會生產許多其餘機型,所以在建立手機的時候須要考慮一個智能機的抽象類.
三、重寫Call方法是因爲智能機可使用更多的擴展實現打電話的功能,好比藍牙、可視等.
四、重寫Message一樣由於智能機能夠處理多媒體,而且能夠輕鬆地與網絡進行交互.
那麼中規中矩,以上是一個比較完整的實現接口與抽象類的實例,但在實際開發中不見得會用到這麼詳細與複雜,可能根本不須要接口,或者根本不須要中間的這麼多抽象類,這裏把這部分描述得如此詳細主要是爲了方便比較兩種方法在實際使用中的差別.
開始比較
面向手機的基本應用
若是簡單的描述電話等能力,彷佛使用抽象類與接口沒有多大區別,前面也講過. 可是若是我手上的項目是一個考慮手機處理的一個項目,須要考慮手機的基本信息,包括電話功能、信息功能和一些簡單的附屬功能、好比鬧鐘等.
很明顯,前文中的代碼中使用Nokia這裏抽象類足夠描述全部信息了. 加上多態的實現,就能夠很好的描述全部手機的基本信息.
在Main方法中添加以下代碼,運行能夠查看結果.
1 Random r = new Random(); 2 3 Nokia[] nks = new Nokia[100]; 4 5 for(int i = 0; i < nks.Length; i++) 6 7 { 8 9 if(r.Next(2) == 1) 10 11 { 12 13 nks[i] = new Nokia1280(); 14 15 } 16 17 else 18 19 { 20 21 nks[i] = new Nokia_Lumia_900(); 22 23 } 24 25 } 26 27 foreach(Nokia n in nks) 28 29 { 30 31 n.Call(); 32 33 n.Message(); 34 35 Console.WriteLine(「手機品牌是{0},外形設計是{1},鍵盤是{2}」, 36 37 n.Mark, 38 39 n.FaceDesigner, 40 41 n.Keyboard 42 43 ); 44 45 Console.WriteLine(); 46 47 }
注:這裏只關注手機的所有基本功能,是面向一個手機對象的,換句話說須要包括全部的手機,但針對最基本和最全面的手機功能來進行實現,不用考慮較爲複雜的手機擴展,很顯然此處不太適合使用接口.
面向智能手機的應用
很明顯,與上面的例子相似,咱們能夠從NokiaApp這個抽象類中派生出其餘型號的智能手機,例如N九、5800、C七、X七、E7等.
要處理智能手機的信息,就可使用NokiaApp這個抽象類與多態的實現,來處理全部的智能手機的打電話、信息處理、軟件處理等全部功能. 很顯然代碼示例與上一個示例較相似,這裏就很少列舉了.
最後要提到的就是,這裏面向的仍然是手機這個對象,所以也不太方便使用接口,應該使用抽象類.
面向電話與信息平臺
上面都使用了抽象類型,可是面對的成員是手機這個看似具體的對象. 可是代碼中仍然有比較專一於功能的地方.
好比我要實現一個短信處理平臺,只須要最基本的短信收發便可. 例如登錄網絡銀行,會受到短信、刷卡消費會受到短信、每日財經消息也會被髮送到手機上等等.
那麼試想一下,此處與手機的什麼最相關?很顯然,只有Message方法,所以這裏使用接口會比較方便. 添加以下代碼,運行便可.
Random r = new Random(); IMobilePhone[] nks = new IMobilePhone[100]; for(int i = 0; i < nks.Length; i++) { if(r.Next(2) == 1) { nks[i] = new Nokia1280(); } else { nks[i] = new Nokia_Lumia_900(); } } foreach(IMobilePhone n in nks) { n.Message(); Console.WriteLine(); }
注:這裏只針對一個Message方法,與什麼手機沒有任何關係,100元的通常手機能夠實現,好幾千元的智能手機也能實現,所以不用考慮那麼複雜,使用接口變得更加容易. 由於此處面向的是功能,是一種能力.
面向運行軟件能力的應用
再來看一個關於智能機應用的例子.
好比我如今要作一個網絡交友平臺,這個顯然是針對智能機設定的一個功能,假定全部手機都已經安裝完成客戶端,我如今的應用只針對用戶發來的數據包,即只負責與用戶進行數據的交互. 那麼徹底沒有必要針對全部智能手機這個對象,只須要使用RunApp方法便可,那麼此處使用接口是再好不過的了.
綜上討論,能夠得出結論了,對於使用邏輯上,接口與抽象類到底用哪個,在於你所寫程序的關注點,你的項目中更關注與功能與方法,那麼使用接口比較好;若果是關注一類對象,那就使用抽象類了. 因此說接口是對一類功能的抽象,抽象類是對一類對象的抽象.
同時不要忘記,對於結構的繼承關係,只能使用接口.
固然再怎麼用,也不會像這樣來寫代碼的,實在是太複雜了.