1.物件導向領域的抽象機制
抽象機制是物件導向的可複用設計的必要條件。物件導向的可複用設計的基石是
開閉原則,也即一個軟體實體應該對擴充開放,對修改關閉。滿足開閉原則的關鍵就在於抽象化。在Java語言裡,可以給出一個或者多個抽象類別或介面,規定出所有的可能的擴充,因此在任何擴充的情況下都不會改變。這就使得系統的抽象層不需修改,從而滿足了開閉原則的第二條:對修改關閉。同時,由於從抽象層匯出一個或者多個新的具體類可以改變系統的行為,因此系統對擴充是開放的,這就滿足了開閉原則的第一條:對擴充開放。所有的軟體系統都有一個共同的性質,即對它們的需求都會隨時間的推移而發生變化,在軟體系統面臨新的需求時,系統的設計必須是穩定的。滿足開閉原則的設計可以給軟體系統帶來一定適應性和靈活性,使變化中的軟體系統具有一定的穩定性和延續性。
抽象類別和介面是Java語言中的兩種定義抽象類別的方式,它們之間有很大的相似性。但是對於它們的選擇卻又往往反映出對於問題領域中的概念本質的理解、對於設計意圖的反映是否正確、合理,因為它們表現了概念間的不同的關係。只有正確理解物件導向的設計原則並靈活使用抽象類別和介面,才能設計出易用的系統。
2.抽象類別和介面的區別
(1)從文法層面區別
從文法層來講,Java語言對於抽象類別和介面給出了不同的定義方式,下面以定義一個名為Demo的抽象類別為例來說明這種不同。Demo抽象類別的定義方式如下:abstract class Demo {abstract void method1();abstract void method2();…}Demo的介面定義方式如下:interface Demo {void method1();void method2();…} 在抽象類別的定義中,Demo可以有自己的資料成員,也可以有非abstract的成員方法,而在介面的定義中,Demo只能夠有static final資料成員,所有的成員方法都是abstract的。從某種意義上說,介面是一種特殊形式的抽象類別。從編程的角度來看,首先,抽象類別和介面的繼承規則不同,抽象只允許單繼承,而一個類卻可以實現多個介面。介面對於多重繼承的支援方面的一種折中考慮;其次,在抽象類別的定義中,可以賦予方法的預設行為,而在介面的定義中,方法不能擁有預設行為,必須使用委託,從某種意義上來說,介面比抽象類別更為抽象化。
(2)從設計層面區別
上面主要從文法層的角度論述了抽象類別和介面的區別,這些層面的區別是比較低層次的、非本質的。本小節將從這個設計層進行分析理解二者概念的本質。抽象類別在Java語言中體現了一種繼承關係,要想使得繼承關係合理,父類和衍生類別之間必須存在"Is-A"關係,即父類和子類在概念本質上應該是相同的。對於介面來說則不然,介面並不要求實現者和介面定義在概念本質上是一致的,僅僅是實現了介面定義的契約而已。考慮這樣一個例子,假設在有一個關於Door的抽象概念,該Door具有執行兩個動作open和close,此時我們可以通過抽象類別或者介面來定義一個表示該抽象概念的類型,定義方式分別如下所示:Door抽象類別的定義方式如下:abstract class Door {abstract void open();abstract void close();}Door的介面定義方式如下:interface Door {void open();void close();}其他具體的Door類型可以extends使用抽象類別方式定義的Door或者implements使用介面方式定義的Door。看起來好像使用抽象類別和介面沒有大的區別。如果現在要求Door還要具有警示的功能。下面將羅列出可能的解決方案,並從設計層對方案進行分析。解決方案一:簡單的在Door的定義中增加一個alarm方法,如下: abstract class Door {abstract void open();abstract void close();abstract void alarm();}或者interface Door {void open();void close();void alarm();}那麼具有警示功能的AlarmDoor的定義方式如下:class AlarmDoor extends Door {void open(){…}void close(){…}void alarm(){…}}或者class AlarmDoor implements Door{void open(){…}void close(){…}void alarm(){…}}這種方法違反了介面隔離原則,在Door的定義中把Door概念本身固有的行為方法和另外一個概念“通報器”的行為方法混在了一起。這樣引起的一個問題是那些僅僅依賴於Door這個概念的模組會因為“通報器”這個概念的改變(比如:修改alarm方法的參數)而改變,反之依然。解決方案二:既然open、close和alarm屬於兩個不同的概念,根據介面隔離原則應該把它們分別定義在代表這兩個概念的抽象類別中。定義方式有:這兩個概念都使用抽象類別方式定義;兩個概念都使用介面方式定義;一個概念使用抽象類別方式定義,另一個概念使用介面方式定義。顯然,由於Java語言不支援多重繼承,所以兩個概念都使用抽象類別方式定義是不可行的。後面兩種方式都是可行的,但是對於它們的選擇卻反映出對於問題領域中的概念本質的理解、對於設計意圖的反映是否正確、合理。如果兩個概念都使用介面方式來定義,那麼就反映出兩個問題:第一,我們可能沒有理解清楚問題領域,AlarmDoor在概念本質上 到底是Door還是通報器?第二,如果我們對於問題領域的理解沒有問題,比如:我們通過對於問題領域的分析發現AlarmDoor在概念本質上和Door是一致的,那麼我們在實現時就沒有能夠正確的揭示我們的設計意圖,因為在這兩個概念的定義上(均使用介面方式定義)反映不出上述含義。如果我們對於問題領域的理解是:AlarmDoor在概念本質上是Door,同時它有具有警示的功能。我們該如何來設計、實現來明確的反映出我們的意思呢?前面已經說過,抽象類別在Java語言中表示一種繼承關係,而繼承關係在本質上是"Is-A"關係。所以對於Door這個概念,我們應該使用抽象類別方式來定義。另外,AlarmDoor又具有警示功能,說明它又能夠完成警示概念中定義的行為,所以警示概念可以通過介面方式定義。如下所示:abstract class Door{abstract void open();abstract void close();}interface Alarm{void alarm();}class AlarmDoor extends Door implements Alarm{void open(){…}void close(){…}void alarm(){…}}這種實現方式基本上能夠明確的反映出我們對於問題領域的理解,正確的揭示我們的設計意圖。其實抽象類別表示的是
"Is-A"關係,interface表示的是"
Has-A"關係,在選擇時可以作為一個依據,當然這是建立在對問題領域的理解上的,比如:如果我們認為AlarmDoor在概念本質上是通報器,同時又具有 Door的功能,那麼上述的定義方式就要反過來了。