abstract class和interface的區別

寫在前面 文中可能很多地方出現了在程序設計中的基礎問題,請各位程序設計的達人不要見笑。爲了區別Java中的關鍵字abstract class和OO編程中的抽象類,我在本文中分別把它們叫做抽象類和抽象的類。 abstract class和interface是Java語言中對於類的抽象的定義進行支持的兩種方式。可是,由於abstract class和interface之間在對於類的抽象方面具有很大的相似性,有的時候甚至可以相互替換,因此很多開發人員在進行類的抽象時,對於 abstract class和interface的選擇顯得比較隨意。其實,兩者之間還是有很大的區別的。對於面向對象的(就是Object Oriented,簡稱OO,也譯做“物件導向的”)方法的理解,對於類的派生關係的理解,都直接影響到在類的抽象過程中的定義。本文主要說明我對於這個問題的一些理解。 理解“抽象”(abstract)的類 抽象是在類的定義之前,需要做的一項複雜的工作。在具體的軟件系統實現之前,我們需要對系統進行設計,其中的一項就是要明確有哪些類需要定義在系統之中,它們之間的關係如何等等。而對於一個經驗不是很豐富的程序員來說,這樣的設計好像又有些勉爲其難了。因爲我們也許根本不知道系統需要哪些類,即使知道必需哪些類,還是對它們的分析不能到達非常清楚的程度。當我們自行開發應用系統的時候,往往在設計出系統需求之後,根本就無從下手。只有當我們拿到一份詳細設計書的時候,我們才知道系統要被做成什麼樣子的。我們似乎覺得實現纔是程序員非常清楚的事情,而非設計。但是沒有人能阻止程序員前進的步伐,因爲程序員不甘於永遠只做程序員。 首先,我們來看傳統的OO編程中的抽象這一過程:當軟件系統中出現了形式相似、本質相同的類的時候,我們需要把這些類的共性拿出來,放到比這些類更高一層的地方(Pull up),做爲這些類的父類(super class),而將它們的不同,保留在這些類中,最後,讓這些類都去繼承共性的父類,以消除重複定義的代碼。在現實中,也有很多相似的情形。比如:如果我們進行一個圖形編輯軟件的開發,就會發現在這個軟件系統中存在着圓、三角形等等一些具體概念,它們是不同的,但是它們又都屬於形狀這樣一個概念,而形狀這一概念不能以一種具體形式出現在在軟件系統中,那麼它就是一個抽象概念。正是因爲抽象的概念在系統中沒有具體的Object與其對應,所以用以表徵抽象概念的抽象的類是不能夠實例化的。在Java中,abstract class和interfac在類的抽象的方面表現有些相似,都可以在Java技術中表現類的共性,都是對類進行抽象後得到的概念性的一種定義。 在OO 的程序設計過程中,抽象的類主要用來進行類的共性的提取。我們可以構造出一個固定的一組行爲的抽象定義,但是對於這一組行爲,每個不同的種類又可能有自己不同的實現方式。這組抽象定義集合起來就是我們通常所說的抽象的類,而派生的非抽象的類中則定義了這一組可能的具體實現。在程序設計過程中,有一個核心的原則——OCP(Open-Closed Principle),是在代碼控制中減少代碼重用和清楚的劃分代碼性質的一個很好的原則。對於不熟悉OCP的朋友,Object Mentor(www.objectmentor.com) 中給出了對OCP的描述:"Open For Extension" and "Closed for Modification",直譯過來是對擴展開放,對修改封閉,但是我並不喜歡這樣去理解,因爲已經有一個成型的翻譯,即弱藕合、強聚合。在O.M.網站上的原文解釋是這樣的: Modules that conform to the open-closed principle have two primary attributes. 1. They are “Open For Extension”. This means that the behavior of the module can be extended. That we can make the module behave in new and different ways as the requirements of the application change, or to meet the needs of new applications. 2. They are “Closed for Modification”. The source code of such a module is inviolate. No one is allowed to make source code changes to it. 事實上,我們在做類的設計及編碼的時候,有可能並不知道其它的類是怎麼樣的,它們有什麼方法,有什麼可用的屬性等等;或許,在我們的類完成後又出現了一種新的情況,即有更多的類適用於當前設計和編碼。而我們卻知道它們的標準,它們的共性,有什麼樣可供使用的方法和屬性。這樣,我們就可以通過對這些類的標準來對新出現的或未知的對象進行操作了。 回到剛纔的例子,我們要做一個圖形圖像軟件的開發,我們需要做一個Drawer以在輸出設備上輸出平面圖形,此時,我們知道有一些圖形,如三角形,圓形等,我們定義了Trianglel、Circle...等圖形,我們要做圖像輸出的時候,需要判斷每一個點是否在形狀的內部等等信息,而對於每個爲類來說,它們的屬性又不盡相同:我們都知道,空間中的三個點確定一個三角形,所以,三角形這個類中,如果有了三個頂點的信息,它就是一個完整的三角形了;而圓卻不同,通過確定圓心座標和半徑就可以確定一個具體的圓形。而我們判斷一個點是否在圖形的內部而把圖形的內部進行填充的時候,我們不能通過每個圖形的每個屬性根據不同的類形來做判斷,而是要根據它們相通的性質來進行判斷。這就要用到抽象的類的技術了。 從語法定義的角度來看abstract class和interface 在語法層面,Java語言對於abstract class和interface給出了不同的定義方式,下面以定義了名爲DemoAbstractClass的抽象類DemoInterface接口和來說明它們之間的異同。 // DemoAbstractClass.java abstract class DemoAbstractClass { // fields goes here ... DataTypeA fieldA = initializeValue; // methods goes here ... ReturnTypeA method1() { } ReturnTypeB method2() { } abstract ReturnTypeC method3(); } // DemoInterface.java interface DemoInterface { // methods goes here ... void method1(); void method2(); } 我想試圖通過上述例子來表現抽象類和接口的異同。通常情況下,抽象類可以擁有自己的數據成員,可以擁有抽象的方法聲明和非抽象的方法定義,其中,抽象的方法聲明的時候需要在方法之前加abstract關鍵字。而在接口的定義中,通常只包含只能夠有靜態的不能被修改的數據成員(也就是必須是static final的,不過在interface中一般不定義數據成員),所有的成員方法都是abstract的。從某種意義上說,interface是一種特殊形式的abstract class。 從編程的角度來看,abstract class和interface都可以用來實現"design by contract"的思想。但是在具體的使用上面還是有一些區別的。 首先,abstract class在Java語言中表示的是一種繼承關係,一個類只能使用一次繼承關係。但是,一個類卻可以實現多個interface。 其次,在abstract class的定義中,允許有非抽象的方法。但是在interface的定義中,並不能允許有非抽象的方法,也就是說,所有的方法都需要在實現者中被具體的定義其方法體。 如果不能在抽象的類中定義默認行爲,就會導致同樣的方法實現出現在該抽象的類的每一個派生類中,違反了"one rule,one place"原則,造成代碼重複,同樣不利於以後的維護。因此,在abstract class和interface間進行選擇時要非常的小心。 從設計思想的角度來看abstract class和interface 上面主要從語法定義和編程的角度論述了abstract class和interface的區別,這些層面的區別是比較低層次的、非本質的。下面我將從設計思想方面,談談我的認識。 前面已經提到過,abstarct class在Java語言中體現了一種繼承關係,要想使得繼承關係合理,父類和派生類之間必須存在"is a"關係,也就是說,派生類是父類的一個具體形式,或者是在某一特定情況下的體現。對於interface 來說則不然,並不要求interface的實現者和interface定義在概念本質上是一致的,僅僅是實現了interface定義的契約而已,換一句話說,就是實現了接口的類和被實現的接口之間的關係是一個"has a ... FUNCTION"的關係。舉個例子: 假設有一個關於門的抽象概念,門具有兩個基本動作open和close,此時我們可以通過abstract class或者interface來定義一個表示這個抽象概念的類型: 使用abstract class方式定義DoorAbstractClass: abstract class DoorAbstractClass { /** @return boolean Returns if the door has been opened successful */ abstract boolean open(); /** @return boolean Returns if the door has been closed successful */ abstract boolean close(); } 使用interface方式定義DoorInterface: interface DoorInterface { /** @return boolean Returns if the door has been opened successful */ boolean open(); /** @return boolean Returns if the door has been closed successful */ boolean close(); } 其他具體的門的類型可以擴展DoorAbstractClass或者實現DoorInterface來完成對類的抽象。目前看來好像使用abstract class和interface沒有大的區別。 如果現在要求門還要具有報警的功能。我們該如何設計類的結構呢?下面將羅列出我想到的解決方案: 解決方案一: 簡單的在上述兩個類的抽象的定義中增加一個alarm方法,如下: abstract class DoorAbstractClass { abstract boolean open(); abstract boolean close(); abstract boolean alarm(); } interface DoorInterface { boolean open(); boolean close(); boolean alarm(); } 那麼具有報警功能的AlarmDoor的定義方式如下: class AlarmDoor1 extends DoorAbstractClass { boolean open() { // method content ... return false; } boolean close() { // method content ... return false; } boolean alarm() { // method content ... return false; } } 或者 class AlarmDoor2 implements DoorInterface { boolean open() { // method content ... return false; } boolean close() { // method content ... return false; } boolean alarm() { // method content ... return false; } } 這種方法在門這個類的定義中把門這個概念本身固有的行爲方法和另外一個概念“報警器”的行爲方法混在了一起。這樣引起了一些問題:那些僅僅依賴於報警門這個類的方法會因爲“報警器”這個類的實現的不同(如:修改alarm方法的參數)而發生改變。它違反了OO設計中的另一個核心原則ISP (Interface Segregation Principle)。 解決方案二: 既然open、close和 alarm屬於兩個不同的概念,根據ISP原則應該把它們分別定義在代表這兩個概念的抽象類中。定義方式有:這兩個抽象的類都使用abstract class方式定義;兩個都使用interface方式定義;一個使用abstract class方式定義,另一個使用interface方式定義。 當然,只有後面兩種方式是可以通過Java的編譯的。但是對於該問題的理解、對於設計意圖的能否合理反映,就決定了選擇哪一種方式。 如果兩個概念都使用interface方式來定義,那麼就反映出兩個現象:1、我們可能根本不清楚“具有報警功能的門”到底是門還是報警器?2、如果我們對於該門是門還是報警器有相對確切的劃分,我們通過分析發現AlarmDoor本來就是一扇門和,那麼我們在實現時就沒有能夠正確的揭示我們的設計意圖,這兩個接口在的定義上(均使用interface方式定義)的地位是均等的,反映不出上述含義。 通常情況下,我們的理解是: AlarmDoor在概念本質上是Door,同時它有具有報警的功能。我們該如何來設計、實現來明確的反映出我們的意思呢?前面已經說過, abstract class在Java語言中表示一種繼承關係,而繼承關係在本質上是"is a"關係。所以對於Door這個概念,我們應該使用abstarct class方式來定義。另外,AlarmDoor又具有報警功能,說明它又能夠完成報警概念中定義的行爲,所以報警概念可以通過interface方式定義。如下所示: abstract class Door { abstract boolean open(); abstract boolean close(); } interface Alarm { boolean alarm(); } class AlarmDoor3 extends Door implements Alarm { boolean open() { // method content ... return false; } boolean close() { // method content ... return false; } boolean alarm() { // method content ... return false; } } 這種實現方式基本上能夠明確的反映出我們對於這個問題的理解,較準確的揭示我們的設計意圖。其實,以abstract class表示"is a"的關係、interface表示"has a ... FUNCTION"的關係作爲依據,在這兩者之間進行選擇還是相對容易的。當然這是被我們所設計和實現的問題所處在的環境所限定的。比如:如果我們認爲 AlarmDoor在本質上是報警器,同時又具有Door的功能,那麼上述的定義方式就要反過來了。 題外:假設我有一套組合音響,它對於我的家來說,可以說是一件陳列品象徵着我的品位,也可以是一件家用電器用來放音樂。當我們考慮實現一套虛擬現實的軟件的時候,我們需要對它進行分類,那麼,一套組合音響到底算一件陳列品還是算一件家用電器怎麼樣區分呢?考慮這樣一個問題的時候,需要了解擁有者的真正用意,就是它用它來做什麼?有時,我們自己也很難區別一套組合音響“是一件”陳列品或者“是一件”家用電器,它“有一種”陳列品的“功能”還是“有一種”家用電器的“功能”。如果當真要在這個問題上面糾纏下去的話,那只有它的使用者才能知道它到底是什麼了。 結論 abstract class和interface是Java技術中的兩種對類抽象的方式,它們之間有很大的相似性。但是對於它們的選擇卻又往往反映出對於問題的理解,是否能正確、合理的反映設計意圖,因爲它們表現了類之間的不同的關係(雖然都能夠實現需求的功能)。這其實也是Java技術的細枝末節,也充分的體現了 Java技術的特點。
發佈了26 篇原創文章 · 獲贊 0 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章