審查徵集貼:http://www.cnblogs.com/BeginnerClassroom/archive/2010/07/30/1788649.html
附錄徵集貼:http://www.cnblogs.com/BeginnerClassroom/archive/2010/08/04/1792175.html
歡迎各位園友對本書的某一部分內容進行拓展,將以附錄的形式附在書後。
要求:
- 緊緊圍繞一兩個中心展開;
- 邏輯清晰,行文流暢;
- 考慮到初學者的基礎。
- 寫作時間最好不要少於一星期。
我寫東西都是寫好以後先放在那裡,過段時間再讀,重新修改,如此反覆幾次,就基本上很流暢了。
(PS:會署名,但無稿費,因為本來就沒多少,不夠分的。當然如果發了大財,我會分給大家的。)
標題 |
作者 |
狀態 |
關於RichTextBox修改字型大小的研究 |
李雨來 |
已完稿 |
委託和介面的策略延遲思想 |
湯非凡 |
已完稿 |
XML格式注釋 |
張智鳴 |
已完稿 |
抽象類別與介面的區別及應用 |
張洋 |
已完稿 |
.NET版本變更史 |
張智鳴 |
已完稿 |
字元編碼 |
趙士敬 |
正在寫 |
流的應用執行個體 |
黃志斌 |
已完稿 |
通過線上評測平台磨礪C#能力 |
曹如進 |
已完稿 |
Regex在EmEditor裡的應用 |
柳永法 |
已完稿 |
C#程式編碼規範 |
顧磊 |
已完稿 |
非同步讀寫操作 |
|
待選 |
控制項開發、自訂控制項 |
MingHao_Hu |
正在寫 |
結構和類的聯絡與區別 |
|
待選 |
繪圖緩衝 |
|
待選 |
Regex應用執行個體 |
空軍 |
正在寫 |
(歡迎您提供其他附錄) |
|
|
抽象類別與介面的區別及應用
(本文由張洋提供)
抽象類別(Abstract Class)與介面(Interface)是物件導向程式設計中兩個重要的概念。由於兩者在自身特性及應用方法上存在諸多相似性,如都不能執行個體化、都可以被繼承(嚴格來說對於介面應該叫做實現),這麼一來,在許多人心中抽象類別與介面的界限非常模糊,對何時該使用抽象類別、何時該使用介面更是感到困惑。
本文的目的是通過對兩者的討論與比較,協助讀者認清抽象類別與介面在思想本質及應用場合方面的區別,如能做到這一點,讀者便可以得心應手地根據具體情況正確選擇和使用抽象類別與介面。
1. 抽象類別與介面是物件導向思想層面概念,不是程式設計語言層面概念
如若想正確認識抽象類別與介面,首先要弄清楚的一點是,這兩個概念均屬於物件導向思想層面,而不屬於某種程式設計語言。例如,C#中用interface關鍵字聲明的語言元素,我們叫它“介面”,其實這是不準確的,準確來說,這應該叫做“介面在C#語言中的實現機制”。
物件導向思想包含許多概念,而不同物件導向語言對這些概念的具體實現機制各有不同。例如,C++中並沒有一種關鍵字對應於C#中的interface,那麼C++中就沒有介面的概念了嗎?非也!在C++中,如果想定義一個介面,可以通過將一個類中所有方法定義為純虛方法[①]來做到。
這裡可以看到,同樣是介面,C#中用interface關鍵字來定義,而C++通過建立一個只包含純虛方法的類來定義,這就是同一種概念在不同具體語言中具有不同的實現機制。類似的,C++中也沒有abstract關鍵字用於定義抽象類別,而是如果一個類中至少含有一個純虛方法且它的方法不全為純虛方法,則這個類被稱為抽象類別。
通過上面的分析可以看出,如果僅僅停留在語言層面去認知抽象類別與介面,是無法準確理解兩者的真諦的,因為不同語言對同一概念的實現機制有很大差別。如果一個C#初學者簡單將兩者理解為“用abstract修飾的類是抽象類別,用interface定義的語言元素是介面”,那麼當他接觸C++時一定會感到困惑,因為C++裡既沒有abstract也沒有interface,而是通過類中純虛方法的情況確定這是個類、是個抽象類別還是個介面。
明確了上面的問題,我們就可以給出抽象類別與介面的真正定義了。
抽象類別是不能執行個體化的類,但是其中的方法可以包含具體實現代碼。
介面是一組方法聲明的集合,其中應僅包含方法的聲明,不能有任何實現代碼。
以上對抽象類別和介面的定義與任何具體語言無關,而是從物件導向思想角度進行的定義,不同語言可以有不同的實現機制。
從上面的定義中,我們可以發現兩者在思想層面上的一項重大區別:抽象類別是類(Class),介面是集合(Set),兩者從本質上不是一種東西。這是我們總結出的第一個區別。請讀者受累將上面加粗的字能放聲朗十遍,聲音越大越好,但是如果被室友或鄰居扔雞蛋請不要找我。
2. 抽象類別是本體的抽象,介面是行為的抽象
在開始這一節之前,我想先請問各位一個問題,“我是一個人”和“我能呼吸”分別表達了“我”和“人”以及“我”和“呼吸”的關係,那麼這兩句話表達的是一種關係嗎?如果你能很容易區分前者表示“是一個”的關係,而後者表示“能”的關係,那麼恭喜你,你一定也能很容易區分抽象類別和介面。
在閱讀這一節時,請讀者務必謹記上面這個問題以及下面這句話:
抽象類別表示“是一個(IS-A)”關係的抽象,介面表示“能(CAN-DO)”關係的抽象。
請照例將上面的大聲話朗讀十遍。
好的,請各位擦乾淨頭上的雞蛋,我們繼續。
從上面粗體字中我們可以看出,抽象類別和介面有一個共性——它們都是“某種關係的抽象”,只不過類型不同罷了。其實如果將上面那句話的前半句中的“抽象類別”改為“類”也是正確的,這並不奇怪,上文我們說過,抽象類別只不過是一種特殊的類罷了。
下面我們先來解釋IS-A關係。其實英語中的IS-A關係在漢語中可以解釋為兩種情況,當IS-A用在一個對象和一個類之間時,意思是“這個對象是類的一個執行個體”,例如關羽是一個對象,我們可以說“GuanYu IS-A General”,其中General(將軍)是個類,這表示關羽是將軍類的一個執行個體。而當IS-A用在兩個類之間時,我認為叫做IS-A-KIND-OF更為準確,表示漢語中的“是一種”,如“General IS-A Person”,表示將軍這個類是人這個類的一種,換用物件導向術語可以如下表述:General是Person的子類(Sub Type),Person是General的父類或超類(Super Type),General繼承自Person。
這後一種IS-A關係,就是抽象類別所表達的關係。分析到這裡可以看出,抽象類別所表達的關係其實就是物件導向三大特性之一——繼承(Inheritance),也就是說,抽象類別所表達的關係,與一般類與類之間的繼承並無區別,而抽象類別相比普通類,除了不能執行個體化外,也並無區別。之所以出現抽象類別,是因為在較高抽象層次上,某些方法(往往是純虛方法)無法實現,必須由其子類按照各自不同的情況具體實現。因為它含有純虛方法,所以將這種類執行個體化在道理上講不通,但我們又希望將這些子類中共有的部分抽象出來減少代碼重複,於是就有了抽象類別——它包含可複用部分,但又不允許執行個體化。
因此,抽象類別的使用動機是在不允許執行個體化的限制下複用代碼。請牢記這個動機。
接著再說說介面和CAN-DO關係。
我們知道,物件導向編程的基本思想就是通過對象間的相互協作,完成程式的功能。具體來說,在物件導向編程中,要求每個類都隱藏內部細節(這叫封裝性),僅對外暴露一組公用方法,對象間就通過互相調用彼此的公用方法完成程式功能。
可以看到,物件導向思想中,對象和對象間根本不需要瞭解,調用者甚至可以完全不知道被調用者是誰,只要知道被調用者“能幹什麼”就行了。這就如同撥打110警示一樣,你根本不知道對方長什麼樣、穿什麼衣服、結沒結婚、有沒有孩子,你也不知道對方在哪,對象是誰,但是你知道對方一定“能接警”,所以你可以順利完成警示。
這種“能幹什麼”就是CAN-DO關係,當我們把這種CAN-DO關係抽象出來,形成一個CAN-DO關係的集合,這就是介面了。那麼使用介面的動機又是什麼呢?動機之一是鬆散耦合。我們知道“低耦合”是物件導向程式設計中一個重要原則,而很大一部分耦合就是調用關係,物件導向中術語叫“依賴”。如果沒有介面,調用者就要緊依賴於被調用者,就如同在沒有110警示的年代,你只認識一個接警員,不知道其他接警員的電話,那麼當你警示時,你必須給這個接警員打電話才行,如果哪天這個接警員休假或病了,你就無法警示了,除非你再去認識一個接警員。這時,我們說你緊依賴於這個接警員,也叫緊耦合。但有了110警示後就不一樣了,我們將“可接警”看作一個介面,介面中有一個方法“接警”,而撥通110後,電話那頭的人一定是實現了這個介面的,這時警示人不再依賴於具體接警員,而是依賴於“可接警”介面,這就叫做松依賴。
所以說,介面又可以看作一組規則的集合,它是對調用者的保證,對被調用者的約束。如上例中,可接警對警示人(調用者)保證調用對象可接警,同時約束接警部門必須把一個實現了這個介面的人安排在接警電話前面。哪怕這是個機器人或剛進行了兩個小時接警培訓的保潔員都沒關係。
使用介面的另一個動機就是實現多態性[②]。
下面想象你被分配到一個全新的研發小組做主管,第一天上班的早晨,一群人站在你面前等著你訓話,你完全不認識他們,也不知道他們各自的職務,但是你可以說一句“都去工作吧”,於是大家作鳥獸散,程式員去寫程式,會計去核對賬目,業務員出門聯絡客戶……當你這樣做的時候,你就利用介面實現了多態性。因為你知道,他們都實現了“可工作”這個介面,雖然各個人員對“工作”具體的實現不一樣,但這不要緊,你只要調用他們的“工作”方法,他們就各自做自己的事情了。如果你不能面向介面去利用多態性,你就要一個個說:“程式員去寫程式,會計去核賬,業務員快出門聯絡客戶……”,這實在非常的費勁。
對這一節的內容做一個總結:
抽象類別表示“是一個(IS-A)”關係的抽象,它抽象了類的本體,其使用動機是在不允許執行個體化的限制下複用代碼。介面表示“能(CAN-DO)”關係的抽象,它抽象了類的行為,其使用動機是鬆散對象間的耦合以及實現程式多態性。
好的,照例念十遍吧,不過這次我允許你默念,因為我怕這次飛來的不是雞蛋而是磚頭。
經過上面的分析,我想你已經可以很容易在抽象類別與介面間做出選擇了。如果你是為了將一系列類的公用代碼抽出,減少代碼的重複,並且這些類與抽象出來的類可以表述為IS-A關係,就用抽象類別;如果你是為了將一個或一組行為抽象出來,用以鬆散對象間耦合或實現多態性,那就用介面吧。
3. C#中抽象類別與介面的探討
這一節我們討論C#語言中一個是人盡皆知的區別:在C#中,一個類最多隻能繼承一個抽象類別,但可以實現多個介面。
如果能充分理解抽象類別對應於IS-A而介面對應於CAN-DO,則對這個約束不會感到奇怪。因為從邏輯上來說,一個類在所有相同抽象層次的類中只能“是其中一個”,但“能幹多種事情”。這裡的相同抽象層次指互相不存在繼承關係的一個全集。
例如,{豬,牛,狗,貓} 可以看作具有相同抽象層次,其某個下層類只能是其中一個的子類,一個類不可能既是牛的子類又是豬的子類,但有可能既是牛的子類又是動物的子類,例如奶牛,這是因為“動物”與“牛”不在一個抽象層次上,“牛”本身就是“動物”的一個子類。
一般的,如果ClassA是ClassB的子類,同時也是ClassC的子類,那麼一定存在ClassB是ClassC的子類或ClassC是ClassB的子類。
換句話說,一個類同時繼承兩個互相沒有繼承關係的類在邏輯上是不成立的。這就說明了為什麼C#中不允許同時繼承一個以上的抽象類別。如果一個類要繼承兩個抽象類別,那麼從邏輯上來說,兩個抽象類別之間必然也存在繼承關係,因此只需讓該類繼承較具體的那個抽象類別即可。例如,本來的設計為“奶牛”同時繼承“牛”和“動物”,但很容易發現,“牛”和“動物”已經存在繼承關係,“牛”是繼承於“動物”的,因此可將繼承關係修改為“奶牛”只繼承“牛”,而讓“牛”繼承於“動物”,這樣就消除了多重繼承。
而介面的CAN-DO關係在邏輯上不存在這樣的矛盾,所以C#允許實現多個介面,具體為什麼請讀者自己思考。
順便說一句,C++中允許多重繼承是因為C++中非抽象類別、抽象類別和介面都用類來實現,而沒有在語言層面區分成不同的語言元素,其實如果設計良好,也是不應該出現對抽象類別的多重繼承的,C#在語言層面上進行了約束,更有利於良好的設計,而C++對這方面比較靈活,需要開發人員自己把握,因此C++對於初學者把握抽象類別與方法更困難一些。
張洋
2010.08.28
leoo2sk.cnblogs.com
[①] C++中的純虛方法指不含有實現代碼的方法,對應於C#中用abstract修飾的方法。
[②] 儘管使用抽象類別有時也是為了實現多態性,但其動機完全沒有使用介面這麼直接,而且我個人更贊同將需要多態的方法抽象成介面的做法。