C#中抽象類與接口的區(qū)別詳解
1.面向接口編程和面向?qū)ο缶幊淌鞘裁搓P(guān)系
首先,面向接口編程和面向?qū)ο缶幊滩⒉皇瞧郊?jí)的,它并不是比面向?qū)ο缶幊谈冗M(jìn)的一種編程思想,而是附屬于面向?qū)ο笏枷塍w系,屬于其一部分?;蛘哒f,它是面向?qū)ο缶幊腆w系中的思想精髓之一。
2.接口的本質(zhì)
接口,在表面上是由幾個(gè)沒有主體代碼的方法定義組成的集合體,有唯一的名稱,可以被類或其他接口所實(shí)現(xiàn)(或者也可以說繼承)。它在形式上可能是如下的樣子:
interface InterfaceName { void Method1(); void Method2(int para1); void Method3(string para2,string para3); }
那么,接口的本質(zhì)是什么呢?或者說接口存在的意義是什么。我認(rèn)為可以從以下兩個(gè)視角考慮:
1)接口是一組規(guī)則的集合,它規(guī)定了實(shí)現(xiàn)本接口的類或接口必須擁有的一組規(guī)則。體現(xiàn)了自然界“如果你是……則必須能……”的理念。
例如,在自然界中,人都能吃飯,即“如果你是人,則必須能吃飯”。那么模擬到計(jì)算機(jī)程序中,就應(yīng)該有一個(gè)IPerson(習(xí)慣上,接口名由“I”開頭)接口,并有一個(gè)方法叫Eat(),然后我們規(guī)定,每一個(gè)表示“人”的類,必須實(shí)現(xiàn)IPerson接口,這就模擬了自然界“如果你是人,則必須能吃飯”這條規(guī)則。
從這里,我想各位也能看到些許面向?qū)ο笏枷氲臇|西。面向?qū)ο笏枷氲暮诵闹?,就是模擬真實(shí)世界,把真實(shí)世界中的事物抽象成類,整個(gè)程序靠各個(gè)類的實(shí)例互相通信、互相協(xié)作完成系統(tǒng)功能,這非常符合真實(shí)世界的運(yùn)行狀況,也是面向?qū)ο笏枷氲木琛?/p>
2)接口是在一定粒度視圖上同類事物的抽象表示。注意這里我強(qiáng)調(diào)了在一定粒度視圖上,因?yàn)?ldquo;同類事物”這個(gè)概念是相對(duì)的,它因?yàn)榱6纫晥D不同而不同。
例如,在我的眼里,我是一個(gè)人,和一頭豬有本質(zhì)區(qū)別,我可以接受我和我同學(xué)是同類這個(gè)說法,但絕不能接受我和一頭豬是同類。但是,如果在一個(gè)動(dòng)物學(xué)家眼里,我和豬應(yīng)該是同類,因?yàn)槲覀兌际莿?dòng)物,他可以認(rèn)為“人”和“豬”都實(shí)現(xiàn)了IAnimal這個(gè)接口,而他在研究動(dòng)物行為時(shí),不會(huì)把我和豬分開對(duì)待,而會(huì)從“動(dòng)物”這個(gè)較大的粒度上研究,但他會(huì)認(rèn)為我和一棵樹有本質(zhì)區(qū)別。
現(xiàn)在換了一個(gè)遺傳學(xué)家,情況又不同了,因?yàn)樯锒寄苓z傳,所以在他眼里,我不僅和豬沒區(qū)別,和一只蚊子、一個(gè)細(xì)菌、一顆樹、一個(gè)蘑菇乃至一個(gè)SARS病毒都沒什么區(qū)別,因?yàn)樗麜?huì)認(rèn)為我們都實(shí)現(xiàn)了IDescendable這個(gè)接口(注:descend vi. 遺傳),即我們都是可遺傳的東西,他不會(huì)分別研究我們,而會(huì)將所有生物作為同類進(jìn)行研究,在他眼里沒有人和病毒之分,只有可遺傳的物質(zhì)和不可遺傳的物質(zhì)。但至少,我和一塊石頭還是有區(qū)別的。
也許你會(huì)覺得我上面的例子像在瞎掰,但是,這正是接口得以存在的意義。面向?qū)ο笏枷牒秃诵闹唤凶龆鄳B(tài)性,什么叫多態(tài)性?說白了就是在某個(gè)粒度視圖層面上對(duì)同類事物不加區(qū)別的對(duì)待而統(tǒng)一處理。而之所以敢這樣做,就是因?yàn)橛薪涌诘拇嬖?。像那個(gè)遺傳學(xué)家,他明白所有生物都實(shí)現(xiàn)了IDescendable接口,那只要是生物,一定有Descend()這個(gè)方法,于是他就可以統(tǒng)一研究,而不至于分別研究每一種生物而最終累死。
可能這里還不能給你一個(gè)關(guān)于接口本質(zhì)和作用的直觀印象。那么在后文的例子和對(duì)幾個(gè)設(shè)計(jì)模式的解析中,你將會(huì)更直觀體驗(yàn)到接口的內(nèi)涵。
3.面向接口編程綜述
那么什么是面向接口編程呢?我個(gè)人的定義是:在系統(tǒng)分析和架構(gòu)中,分清層次和依賴關(guān)系,每個(gè)層次不是直接向其上層提供服務(wù)(即不是直接實(shí)例化在上層中),而是通過定義一組接口,僅向上層暴露其接口功能,上層對(duì)于下層僅僅是接口依賴,而不依賴具體類。
這樣做的好處是顯而易見的,首先對(duì)系統(tǒng)靈活性大有好處。當(dāng)下層需要改變時(shí),只要接口及接口功能不變,則上層不用做任何修改。甚至可以在不改動(dòng)上層代碼時(shí)將下層整個(gè)替換掉,就像我們將一個(gè)WD的60G硬盤換成一個(gè)希捷的160G的硬盤,計(jì)算機(jī)其他地方不用做任何改動(dòng),而是把原硬盤拔下來、新硬盤插上就行了,因?yàn)橛?jì)算機(jī)其他部分不依賴具體硬盤,而只依賴一個(gè)IDE接口,只要硬盤實(shí)現(xiàn)了這個(gè)接口,就可以替換上去。從這里看,程序中的接口和現(xiàn)實(shí)中的接口極為相似,所以我一直認(rèn)為,接口(interface)這個(gè)詞用的真是神似!
使用接口的另一個(gè)好處就是不同部件或?qū)哟蔚拈_發(fā)人員可以并行開工,就像造硬盤的不用等造CPU的,也不用等造顯示器的,只要接口一致,設(shè)計(jì)合理,完全可以并行進(jìn)行開發(fā),從而提高效率。
對(duì)本文的補(bǔ)充:
1.關(guān)于“面向接口編程”中的“接口”與具體面向?qū)ο笳Z言中“接口”兩個(gè)詞
看到有朋友提出“面向接口編程”中的“接口”二字應(yīng)該比單純編程語言中的interface范圍更大。我經(jīng)過思考,覺得很有道理。這里我寫的確實(shí)不太合理。我想,面向?qū)ο笳Z言中的“接口”是指具體的一種代碼結(jié)構(gòu),例如C#中用interface關(guān)鍵字定義的接口。而“面向接口編程”中的“接口”可以說是一種從軟件架構(gòu)的角度、從一個(gè)更抽象的層面上指那種用于隱藏具體底層類和實(shí)現(xiàn)多態(tài)性的結(jié)構(gòu)部件。從這個(gè)意義上說,如果定義一個(gè)抽象類,并且目的是為了實(shí)現(xiàn)多態(tài),那么我認(rèn)為把這個(gè)抽象類也稱為“接口”是合理的。但是用抽象類實(shí)現(xiàn)多態(tài)合理不合理?在下面第二條討論。
概括來說,我覺得兩個(gè)“接口”的概念既相互區(qū)別又相互聯(lián)系。“面向接口編程”中的接口是一種思想層面的用于實(shí)現(xiàn)多態(tài)性、提高軟件靈活性和可維護(hù)性的架構(gòu)部件,而具體語言中的“接口”是將這種思想中的部件具體實(shí)施到代碼里的手段。
2.關(guān)于抽象類與接口
如果單從具體代碼來看,對(duì)這兩個(gè)概念很容易模糊,甚至覺得接口就是多余的,因?yàn)閱螐木唧w功能來看,除多重繼承外(C#,Java中),抽象類似乎完全能取代接口。但是,難道接口的存在是為了實(shí)現(xiàn)多重繼承?當(dāng)然不是。我認(rèn)為,抽象類和接口的區(qū)別在于使用動(dòng)機(jī)。使用抽象類是為了代碼的復(fù)用,而使用接口的動(dòng)機(jī)是為了實(shí)現(xiàn)多態(tài)性。所以,如果你在為某個(gè)地方該使用接口還是抽象類而猶豫不決時(shí),那么可以想想你的動(dòng)機(jī)是什么。
看到有朋友對(duì)IPerson這個(gè)接口的質(zhì)疑,我個(gè)人的理解是,IPerson這個(gè)接口該不該定義,關(guān)鍵看具體應(yīng)用中是怎么個(gè)情況。如果我們的項(xiàng)目中有Women和Man,都繼承Person,而且Women和Man絕大多數(shù)方法都相同,只有一個(gè)方法DoSomethingInWC()不同(例子比較粗俗,各位見諒),那么當(dāng)然定義一個(gè)AbstractPerson抽象類比較合理,因?yàn)樗梢园哑渌蟹椒ǘ及?,子類只定義DoSomethingInWC(),大大減少了重復(fù)代碼量。
但是,如果我們程序中的Women和Man兩個(gè)類基本沒有共同代碼,而且有一個(gè)PersonHandle類需要實(shí)例化他們,并且不希望知道他們是男是女,而只需把他們當(dāng)作人看待,并實(shí)現(xiàn)多態(tài),那么定義成接口就有必要了。
總而言之,接口與抽象類的區(qū)別主要在于使用的動(dòng)機(jī),而不在于其本身。而一個(gè)東西該定義成抽象類還是接口,要根據(jù)具體環(huán)境的上下文決定。
再者,我認(rèn)為接口和抽象類的另一個(gè)區(qū)別在于,抽象類和它的子類之間應(yīng)該是一般和特殊的關(guān)系,而接口僅僅是它的子類應(yīng)該實(shí)現(xiàn)的一組規(guī)則。(當(dāng)然,有時(shí)也可能存在一般與特殊的關(guān)系,但我們使用接口的目的不在這里)如,交通工具定義成抽象類,汽車、飛機(jī)、輪船定義成子類,是可以接受的,因?yàn)槠?、飛機(jī)、輪船都是一種特殊的交通工具。再譬如Icomparable接口,它只是說,實(shí)現(xiàn)這個(gè)接口的類必須要可以進(jìn)行比較,這是一條規(guī)則。如果Car這個(gè)類實(shí)現(xiàn)了Icomparable,只是說,我們的Car中有一個(gè)方法可以對(duì)兩個(gè)Car的實(shí)例進(jìn)行比較,可能是比哪輛車更貴,也可能比哪輛車更大,這都無所謂,但我們不能說“汽車是一種特殊的可以比較”,這在文法上都不通。
C#.NET里面抽象類和接口有什么區(qū)別?
接口和抽象類的概念不一樣。接口是對(duì)動(dòng)作的抽象,抽象類是對(duì)根源的抽象。
抽象類表示的是,這個(gè)對(duì)象是什么。接口表示的是,這個(gè)對(duì)象能做什么。比如,男人,女人,這兩個(gè)類(如果是類的話……),他們的抽象類是人。說明,他們都是人。
人可以吃東西,狗也可以吃東西,你可以把“吃東西”定義成一個(gè)接口,然后讓這些類去實(shí)現(xiàn)它.
所以,在高級(jí)語言上,一個(gè)類只能繼承一個(gè)類(抽象類)(正如人不可能同時(shí)是生物和非生物),但是可以實(shí)現(xiàn)多個(gè)接口(吃飯接口、走路接口)。
下面接著再說說兩者在應(yīng)用上的區(qū)別:
接口更多的是在系統(tǒng)架構(gòu)設(shè)計(jì)方法發(fā)揮作用,主要用于定義模塊之間的通信契約。
而抽象類在代碼實(shí)現(xiàn)方面發(fā)揮作用,可以實(shí)現(xiàn)代碼的重用
模板方法設(shè)計(jì)模式是抽象類的一個(gè)典型應(yīng)用
最佳答案:
1抽象類
(1) 抽象方法只作聲明,而不包含實(shí)現(xiàn),可以看成是沒有實(shí)現(xiàn)體的虛方法
(2) 抽象類不能被實(shí)例化
(3) 抽象類可以但不是必須有抽象屬性和抽象方法,但是一旦有了抽象方法,就一定要把這個(gè)類聲明為抽象類
(4) 具體派生類必須覆蓋基類的抽象方法
(5) 抽象派生類可以覆蓋基類的抽象方法,也可以不覆蓋。如果不覆蓋,則其具體派生類必須覆蓋它們。如:
using System; public abstract class A //抽象類A { private int num=0; public int Num //抽象類包含屬性 { get { return num; } set { num = value; } } public virtual int getNum() //抽象類包含虛方法 { return num; } public void setNum(int n) // //抽象類包含普通方法 { this.num = n; } public abstract void E(); //類A中的抽象方法E } public abstract class B : A //由于類B繼承了類A中的抽象方法E,所以類B也變成了抽象類 { } public class C : B { public override void E() //重寫從類A繼承的抽象方法。如果類B自己還定義了抽象方法,也必須重寫 { //throw new Exception("The method or operation is not implemented."); } } public class Test { static void Main() { C c = new C(); c.E(); } }
二、接 口
(1) 接口不能被實(shí)例化
(2) 接口只能包含方法聲明
(3) 接口的成員包括方法、屬性、索引器、事件
(4) 接口中不能包含常量、字段(域)、構(gòu)造函數(shù)、析構(gòu)函數(shù)、靜態(tài)成員。如:
public delegate void EventHandler(object sender, Event e); public interface ITest { //int x = 0; int A { get; set; } void Test(); event EventHandler Event; int this[int index] { get; set; } }
(5) 接口中的所有成員默認(rèn)為public,因此接口中不能有private修飾符
(6) 派生類必須實(shí)現(xiàn)接口的所有成員
(7) 一個(gè)類可以直接實(shí)現(xiàn)多個(gè)接口,接口之間用逗號(hào)隔開
(8) 一個(gè)接口可以有多個(gè)父接口,實(shí)現(xiàn)該接口的類必須實(shí)現(xiàn)所有父接口中的所有成員
三、抽象類和接口
相同點(diǎn):
(1) 都可以被繼承
(2) 都不能被實(shí)例化
(3) 都可以包含方法聲明
(4) 派生類必須實(shí)現(xiàn)未實(shí)現(xiàn)的方法
區(qū) 別:
(1) 抽象基類可以定義字段、屬性、方法實(shí)現(xiàn)。接口只能定義屬性、索引器、事件、和方法聲明,不能包含字段。
(2) 抽象類是一個(gè)不完整的類,需要進(jìn)一步細(xì)化,而接口是一個(gè)行為規(guī)范。微軟的自定義接口總是后帶able字段,證明其是表述一類“我能做。。。”
(3) 接口可以被多重實(shí)現(xiàn),抽象類只能被單一繼承
(4) 抽象類更多的是定義在一系列緊密相關(guān)的類間,而接口大多數(shù)是關(guān)系疏松但都實(shí)現(xiàn)某一功能的類中
(5) 抽象類是從一系列相關(guān)對(duì)象中抽象出來的概念, 因此反映的是事物的內(nèi)部共性;接口是為了滿足外部調(diào)用而定義的一個(gè)功能約定, 因此反映的是事物的外部特性
(6) 接口基本上不具備繼承的任何具體特點(diǎn),它僅僅承諾了能夠調(diào)用的方法
(7) 接口可以用于支持回調(diào),而繼承并不具備這個(gè)特點(diǎn)
(8) 抽象類實(shí)現(xiàn)的具體方法默認(rèn)為虛的,但實(shí)現(xiàn)接口的類中的接口方法卻默認(rèn)為非虛的,當(dāng)然您也可以聲明為虛的
(9) 如果抽象類實(shí)現(xiàn)接口,則可以把接口中方法映射到抽象類中作為抽象方法而不必實(shí)現(xiàn),而在抽象類的子類中實(shí)現(xiàn)接口中方法
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時(shí)也希望多多支持腳本之家!
相關(guān)文章
C#實(shí)現(xiàn)操作windows系統(tǒng)服務(wù)(service)的方法
這篇文章主要介紹了C#實(shí)現(xiàn)操作windows系統(tǒng)服務(wù)(service)的方法,可實(shí)現(xiàn)系統(tǒng)服務(wù)的啟動(dòng)和停止功能,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04jQuery uploadify在谷歌和火狐瀏覽器上傳失敗的解決方案
jquery.uploadify插件是一個(gè)基于jquery來實(shí)現(xiàn)上傳的,這個(gè)插件很好用,每一次向后臺(tái)發(fā)送數(shù)據(jù)流請(qǐng)求時(shí),ie會(huì)自動(dòng)把本地cookie存儲(chǔ)捆綁在一起發(fā)送給服務(wù)器。但firefox、chrome不會(huì)這樣做,他們會(huì)認(rèn)為這樣不安全,下面介紹下jQuery uploadify上傳失敗的解決方案2015-08-08C#利用Spire.Pdf包實(shí)現(xiàn)為PDF添加數(shù)字簽名
Spire.PDF for .NET 是一款專業(yè)的基于.NET平臺(tái)的PDF文檔控制組件。它能夠讓開發(fā)人員在不使用Adobe Acrobat和其他外部控件的情況下,運(yùn)用.NET 應(yīng)用程序創(chuàng)建,閱讀,編寫和操縱PDF 文檔。本文將利用其實(shí)現(xiàn)添加數(shù)字簽名,需要的可以參考一下2022-08-08