java學習筆記之面向接口編程

Java面向接口編程

前言:
一直以來,我都習慣了使用文學的閱讀習慣去學習計算機這門應用工科學科,但其實沒有面對實際問題的情況下,單純的學習理論知識,用不了多久就忘了,也沒法理解得深刻,可我又是個不適合專研代碼的工科生,所以我也一直在尋找一種最適合自己的學習方法,發揮自己聯想快,總結快的思維特點,也許現在一直還未發掘到,但希望可以在程序員的路上走得越來越輕鬆,也越來越開心。

下面回到正題,該怎麼解釋面向接口編程?

一、面向接口編程和麪向對象編程是什麼關係

面向接口編程和麪向對象編程並不是平級的,它並不是比面向對象編程更先進的一種獨立的編程思想,而是附屬於面向對象思想體系,屬於其一部分。或者說,它是面向對象編程體系中的思想精髓之一。

二、接口的本質

接口,在表面上是由幾個沒有主體代碼的方法定義組成的集合體,有唯一的名稱,可以被類或其他接口所實現(或者也可以說繼承)。它在形式上可能是如下的樣子:

interface InterfaceName
{
    void Method1();
    void Method2(int para1);
    void Method3(string para2,string para3);
}

那麼,接口的本質是什麼呢?或者說接口存在的意義是什麼。可以從以下兩個視角考慮:

1)接口是一組規則的集合,它規定了實現本接口的類或接口必須擁有的一組規則。體現了自然界“如果你是……則必須能……”的理念。

例如,在自然界中,人都能吃飯,即“如果你是人,則必須能吃飯”。那麼模擬到計算機程序中,就應該有一個IPerson(習慣上,接口名由“I”開頭)接口,並有一個方法叫Eat(),然後我們規定,每一個表示“人”的類,必須實現IPerson接口,這就模擬了自然界“如果你是人,則必須能吃飯”這條規則。

從這裏,我想各位也能看到些許面向對象思想的東西。面向對象思想的核心之一,就是模擬真實世界,把真實世界中的事物抽象成類,整個程序靠各個類的實例互相通信、互相協作完成系統功能,這非常符合真實世界的運行狀況,也是面向對象思想的精髓。

2)接口是在一定粒度視圖上同類事物的抽象表示。

注意這裏我強調了在一定粒度視圖上,因爲“同類事物”這個概念是相對的,它因爲粒度視圖不同而不同。

例如,在我的眼裏,我是一個人,和一頭豬有本質區別,我可以接受我和我同學是同類這個說法,但絕不能接受我和一頭豬是同類。但是,如果在一個動物學家眼裏,我和豬應該是同類,因爲我們都是動物,他可以認爲“人”和“豬”都實現了IAnimal這個接口,而他在研究動物行爲時,不會把我和豬分開對待,而會從“動物”這個較大的粒度上研究,但他會認爲我和一棵樹有本質區別。

現在換了一個遺傳學家,情況又不同了,因爲生物都能遺傳,所以在他眼裏,我不僅和豬沒區別,和一隻蚊子、一個細菌、一顆樹、一個蘑菇乃至一個SARS病毒都沒什麼區別,因爲他會認爲我們都實現了IDescendable這個接口(注:descend vi. 遺傳),即我們都是可遺傳的東西,他不會分別研究我們,而會將所有生物作爲同類進行研究,在他眼裏沒有人和病毒之分,只有可遺傳的物質和不可遺傳的物質。但至少,我和一塊石頭還是有區別的。

可不幸的事情發生了,某日,地球上出現了一位偉大的人,他叫列寧,他在熟讀馬克思、恩格斯的辯證唯物主義思想鉅著後,頗有心得,於是他下了一個著名的定義:所謂物質,就是能被意識所反映的客觀實在。至此,我和一塊石頭、一絲空氣、一條成語和傳輸手機信號的電磁場已經沒什麼區別了,因爲在列寧的眼裏,我們都是可以被意識所反映的客觀實在。如果列寧是一名程序員,他會這麼說:所謂物質,就是所有同時實現了“IReflectabe”和“IEsse”兩個接口的類所生成的實例。(注:reflect v. 反映 esse n. 客觀實在)

也許你會覺得上面的例子像在瞎掰,但是,這正是接口得以存在的意義。

面向對象思想和核心之一叫做多態性,什麼叫多態性?
說白了就是在某個粒度視圖層面上對同類事物不加區別的對待而統一處理(//我記得Java的多態性體現在編譯和執行的不同處理)。而之所以敢這樣做,就是因爲有接口的存在。

像那個遺傳學家,他明白所有生物都實現了IDescendable接口,那只要是生物,一定有Descend()這個方法,於是他就可以統一研究,而不至於分別研究每一種生物而最終累死。

三、那麼什麼是面向接口編程呢

個人的定義是:在系統分析和架構中,分清層次和依賴關係,每個層次不是直接向其上層提供服務(即不是直接實例化在上層中),而是通過定義一組接口,僅向上層暴露其接口功能,上層對於下層僅僅是接口依賴,而不依賴具體類。

這樣做的好處是顯而易見的,首先對系統靈活性大有好處。當下層需要改變時,只要接口及接口功能不變,則上層不用做任何修改。甚至可以在不改動上層代碼時將下層整個替換掉,就像我們將一個WD的60G硬盤換成一個希捷的160G的硬盤,計算機其他地方不用做任何改動,而是把原硬盤拔下來、新硬盤插上就行了,因爲計算機其他部分不依賴具體硬盤,而只依賴一個IDE接口,只要硬盤實現了這個接口,就可以替換上去。從這裏看,程序中的接口和現實中的接口極爲相似,所以我一直認爲,接口(interface)這個詞用的真是神似!

使用接口的另一個好處就是不同部件或層次的開發人員可以並行開工,就像造硬盤的不用等造CPU的,也不用等造顯示器的,只要接口一致,設計合理,完全可以並行進行開發,從而提高效率。

核心的說:面向對象的精髓是模擬現實,這也可以說是Java學習的靈魂。所以,多從現實中思考面向對象的東西,對提高系統分析設計能力大有脾益。

3.1、關於“面向接口編程”中的“接口”與具體面嚮對象語言中“接口”兩個詞

面嚮對象語言中的“接口”是指具體的一種代碼結構,例如C#中用interface關鍵字定義的接口。而“面向接口編程”中的“接口”可以說是一種從軟件架構的角度、從一個更抽象的層面上指那種用於隱藏具體底層類和實現多態性的結構部件。從這個意義上說,如果定義一個抽象類,並且目的是爲了實現多態,那麼我認爲把這個抽象類也稱爲“接口”是合理的。

概括來說,兩個“接口”的概念既相互區別又相互聯繫。

“面向接口編程”中的接口是一種思想層面的用於實現多態性、提高軟件靈活性和可維護性的架構部件,而具體語言中的“接口”是將這種思想中的部件具體實施到代碼裏的手段。

3.2、關於抽象類與接口

1
如果單從具體代碼來看,對這兩個概念很容易模糊,甚至覺得接口就是多餘的,因爲單從具體功能來看,除多重繼承外(C#,Java中),抽象類似乎完全能取代接口。但是,難道接口的存在是爲了實現多重繼承?當然不是。

抽象類和接口的區別在於使用動機。使用抽象類是爲了代碼的複用,而使用接口的動機是爲了實現多態性。所以,如果你在爲某個地方該使用接口還是抽象類而猶豫不決時,那麼可以想想你的動機是什麼。

例如,對IPerson這個接口的質疑,我個人的理解是,IPerson這個接口該不該定義,關鍵看具體應用中是怎麼個情況。如果我們的項目中有Women和Man,都繼承Person,而且Women和Man絕大多數方法都相同,只有一個方法DoSomethingInWC()不同,那麼當然定義一個AbstractPerson抽象類比較合理,因爲它可以把其他所有方法都包含進去,子類只定義DoSomethingInWC(),大大減少了重複代碼量。

但是,如果我們程序中的Women和Man兩個類基本沒有共同代碼,而且有一個PersonHandle類需要實例化他們,並且不希望知道他們是男是女,而只需把他們當作人看待,並實現多態,那麼定義成接口就有必要了。

總而言之,接口與抽象類的區別主要在於使用的動機,而不在於其本身。而一個東西該定義成抽象類還是接口,要根據具體環境的上下文決定。

再者,接口和抽象類的另一個區別在於,抽象類和它的子類之間應該是一般和特殊的關係,而接口僅僅是它的子類應該實現的一組規則。(當然,有時也可能存在一般與特殊的關係,但我們使用接口的目的不在這裏)

如,交通工具定義成抽象類,汽車、飛機、輪船定義成子類,是可以接受的,因爲汽車、飛機、輪船都是一種特殊的交通工具。再譬如Icomparable接口,它只是說,實現這個接口的類必須要可以進行比較,這是一條規則。如果Car這個類實現了Icomparable,只是說,我們的Car中有一個方法可以對兩個Car的實例進行比較,可能是比哪輛車更貴,也可能比哪輛車更大,這都無所謂,但我們不能說“汽車是一種特殊的可以比較”,這在文法上都不通 。

2

abstract class和interface是Java語言中對於抽象類定義進行支持的兩種機制,正是由於這兩種機制的存在,才賦予了Java強大的面向對象能力。 abstract class和interface之間在對於抽象類定義的支持方面具有很大的相似性,甚至可以相互替換,因此很多開發者在進行抽象類定義時對於 abstract class和interface的選擇顯得比較隨意。

其實,兩者之間還是有很大的區別的,對於它們的選擇甚至反映出對於問題領域本質的理解、對於設計意圖的理解是否正確、合理。

2.1、理解抽象類

abstract class和interface在Java語言中都是用來進行抽象類(本文中的抽象類並非從abstract class翻譯而來,它表示的是一個抽象體,而abstract class爲Java語言中用於定義抽象類的一種方法,請讀者注意區分)定義的,那麼什麼是抽象類,使用抽象類能爲我們帶來什麼好處呢?

在面向對象的概念中,我們知道所有的對象都是通過類來描繪的,但是反過來卻不是 這樣。並不是所有的類都是用來描繪對象的,如果一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類。抽象類往往用來表徵我們在對問題領 域進行分析、設計中得出的抽象概念,是對一系列看上去不同,但是本質上相同的具體概念的抽象。

比如:如果我們進行一個圖形編輯軟件的開發,就會發現問題領域存在着圓、三角形 這樣一些具體概念,它們是不同的,但是它們又都屬於形狀這樣一個概念,形狀這個概念在問題領域是不存在的,它就是一個抽象概念。正是因爲抽象的概念在問題 領域沒有對應的具體概念,所以用以表徵抽象概念的抽象類是不能夠實例化的。

在面向對象領域,抽象類主要用來進行類型隱藏。我們可以構造出一個固定的一組行 爲的抽象描述,但是這組行爲卻能夠有任意個可能的具體實現方式。這個抽象描述就是抽象類,而這一組任意個可能的具體實現則表現爲所有可能的派生類。模塊可 以操作一個抽象體。由於模塊依賴於一個固定的抽象體,因此它可以是不允許修改的;同時,通過從這個抽象體派生,也可擴展此模塊的行爲功能。熟悉OCP的讀 者一定知道,爲了能夠實現面向對象設計的一個最核心的原則OCP(Open-Closed Principle),抽象類是其中的關鍵所在。

2.2、從語法定義層面看abstract class和interface

在語法層面,Java語言對於abstract class和interface給出了不同的定義方式,下面以定義一個名爲Demo的抽象類爲例來說明這種不同。使用abstract class的方式定義Demo抽象類的方式如下:

abstract class Demo

abstract void method1();    

abstract void method2();    

…      
}    

使用interface的方式定義Demo抽象類的方式如下:

interface Demo {    

void method1();    

void method2();    

…    
}    

}    

在abstract class方式中,Demo可以有自己的數據成員,也可以有非abstarct的成員方法,而在interface方式的實現中,Demo只能夠有靜態的 不能被修改的數據成員(也就是必須是static final的,不過在interface中一般不定義數據成員),所有的成員方法都是abstract的。從某種意義上說,interface是一種特殊 形式的abstract class。

從編程的角度來看,abstract class和interface都可以用來實現”design by contract”的思想。但是在具體的使用上面還是有一些區別的。

首先,abstract class在Java語言中表示的是一種繼承關係,一個類只能使用一次繼承關係。但是,一個類卻可以實現多個interface。也許,這是Java語言的設計者在考慮Java對於多重繼承的支持方面的一種折中考慮吧。

其次,在abstract class的定義中,我們可以賦予方法的默認行爲。但是在interface的定義中,方法卻不能擁有默認行爲,爲了繞過這個限制,必須使用委託,但是這會 增加一些複雜性,有時會造成很大的麻煩。

在抽象類中不能定義默認行爲還存在另一個比較嚴重的問題,那就是可能會造成維護上的 麻煩。因爲如果後來想修改類的界面(一般通過abstract class或者interface來表示)以適應新的情況(比如,添加新的方法或者給已用的方法中添加新的參數)時,就會非常的麻煩,可能要花費很多的時 間(對於派生類很多的情況,尤爲如此)。但是如果界面是通過abstract class來實現的,那麼可能就只需要修改定義在abstract class中的默認行爲就可以了。

同樣,如果不能在抽象類中定義默認行爲,就會導致同樣的方法實現出現在該抽象類 的每一個派生類中,違反了”one rule,one place”原則,造成代碼重複,同樣不利於以後的維護。因此,在abstract class和interface間進行選擇時要非常的小心。

2.3、從設計理念層面看abstract class和interface

上面主要從語法定義和編程的角度論述了abstract class和interface的區別,這些層面的區別是比較低層次的、非本質的。本文將從另一個層面:abstract class和interface所反映出的設計理念,來分析一下二者的區別。作者認爲,從這個層面進行分析才能理解二者概念的本質所在。

前面已經提到過,abstarct class在Java語言中體現了一種繼承關係,要想使得繼承關係合理,父類和派生類之間必須存在”is a”關係,即父類和派生類在概念本質上應該是相同的。對於interface 來說則不然,並不要求interface的實現者和interface定義在概念本質上是一致的,僅僅是實現了interface定義的契約而已。爲了使 論述便於理解,下面將通過一個簡單的實例進行說明。

考慮這樣一個例子,假設在我們的問題領域中有一個關於Door的抽象概念,該Door具有執行兩個動作open和close,此時我們可以通過abstract class或者interface來定義一個表示該抽象概念的類型,定義方式分別如下所示:

使用abstract class方式定義Door:

abstract class Door {    

abstract void open();    

abstract void close();    

}    

使用interface方式定義Door:

interface Door {    

void open();    

void close();    
}    

其他具體的Door類型可以extends使用abstract class方式定義的Door或者implements使用interface方式定義的Door。看起來好像使用abstract class和interface沒有大的區別。

如果現在要求Door還要具有報警的功能。我們該如何設計針對該例子的類結構呢(在 本例中,主要是爲了展示abstract class和interface反映在設計理念上的區別,其他方面無關的問題都做了簡化或者忽略)下面將羅列出可能的解決方案,並從設計理念層面對這些不 同的方案進行分析。

解決方案一:

簡單的在Door的定義中增加一個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() { … }    
}    

這種方法違反了面向對象設計中的一個核心原則ISP(Interface Segregation Priciple),在Door的定義中把Door概念本身固有的行爲方法和另外一個概念”報警器”的行爲方法混在了一起。這樣引起的一個問題是那些僅僅 依賴於Door這個概念的模塊會因爲”報警器”這個概念的改變(比如:修改alarm方法的參數)而改變,反之依然。

解決方案二:

既然open、close和alarm屬於兩個不同的概念,根據ISP原則應該把它 們分別定義在代表這兩個概念的抽象類中。定義方式有:這兩個概念都使用abstract class方式定義;兩個概念都使用interface方式定義;一個概念使用abstract class方式定義,另一個概念使用interface方式定義。

顯然,由於Java語言不支持多重繼承,所以兩個概念都使用abstract class方式定義是不可行的。後面兩種方式都是可行的,但是對於它們的選擇卻反映出對於問題領域中的概念本質的理解、對於設計意圖的反映是否正確、合理。我們一一來分析、說明。

如果兩個概念都使用interface方式來定義,那麼就反映出兩個問題:

1、我們可能沒有理解清楚問題領域,AlarmDoor在概念本質上到底是Door還是報警器?

2、如果我們對於問題領域的理解沒有問題,比如:我們通過對於問題領域的分析發現 AlarmDoor在概念本質上和Door是一致的,那麼我們在實現時就沒有能夠正確的揭示我們的設計意圖,因爲在這兩個概念的定義上(均使用 interface方式定義)反映不出上述含義。

如果我們對於問題領域的理解是:AlarmDoor在概念本質上是Door,同 時它有具有報警的功能。我們該如何來設計、實現來明確的反映出我們的意思呢?前面已經說過,abstract class在Java語言中表示一種繼承關係,而繼承關係在本質上是”is a”關係。所以對於Door這個概念,我們應該使用abstarct class方式來定義。另外,AlarmDoor又具有報警功能,說明它又能夠完成報警概念中定義的行爲,所以報警概念可以通過interface方式定 義。如下所示:

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() { … }    

}    

這種實現方式基本上能夠明確的反映出我們對於問題領域的理解,正確的揭示我們的設計 意圖。其實abstract class表示的是”is a”關係,interface表示的是”like a”關係,大家在選擇時可以作爲一個依據,當然這是建立在對問題領域的理解上的,比如:如果我們認爲AlarmDoor在概念本質上是報警器,同時又具有 Door的功能,那麼上述的定義方式就要反過來了。

abstract class和interface是Java語言中的兩種定義抽象類的方式,它們之間有很大的相似性。但是對於它們的選擇卻又往往反映出對於問題領域中的概 念本質的理解、對於設計意圖的反映是否正確、合理,因爲它們表現了概念間的不同的關係(雖然都能夠實現需求的功能)。這其實也是語言的一種的慣用法。

總結幾句話來說:

1、抽象類和接口都不能直接實例化,如果要實例化,抽象類變量必須指向實現所有抽象方法的子類對象,接口變量必須指向實現所有接口方法的類對象。

2、抽象類要被子類繼承,接口要被類實現。

3、接口只能做方法申明,抽象類中可以做方法申明,也可以做方法實現

4、接口裏定義的變量只能是公共的靜態的常量,抽象類中的變量是普通變量。

5、抽象類裏的抽象方法必須全部被子類所實現,如果子類不能全部實現父類抽象方法,那麼該子類只能是抽象類。同樣,一個實現接口的時候,如不能全部實現接口方法,那麼該類也只能爲抽象類。

6、抽象方法只能申明,不能實現。abstract void abc();不能寫成abstract void abc(){}。

7、抽象類裏可以沒有抽象方法

8、如果一個類裏有抽象方法,那麼這個類只能是抽象類

9、抽象方法要被實現,所以不能是靜態的,也不能是私有的。

10、接口可繼承接口,並可多繼承接口,但類只能單根繼承。

特別是對於公用的實現代碼,抽象類有它的優點。抽象類能夠保證實現的層次關係,避免代碼重複。然而,即使在使用抽 象類的場合,也不要忽視通過接口定義行爲模型的原則。從實踐的角度來看,如果依賴於抽象類來定義行爲,往往導致過於複雜的繼承關係,而通過接口定義行爲能 夠更有效地分離行爲與實現,爲代碼的維護和修改帶來方便。

四、通過簡單的例子說明

1、 建立一個接口類,並聲明一系列的方法(規則)

/** 
 * 定義PCI接口,定義主板PCI插槽規範 
 * @author xiaodeng 
 * 
 */  
public interface PCI {
    void start();
    void stop();
}

2、定義業務層

/** 
 * 通過這個方法,主板上可以插入任何實現PCI接口規範的卡 
 *  
 * @param pci 參數類型爲接口,任何實現接口的類都可以傳進來. 
 */  
public class MainBoard {
public void usePCICard(PCI pci)
{
    pci.start();
    pci.stop();
    }
}

3、定義符合接口規範的具體類
3.1 顯卡類:

/** 
 * 通過這個方法,主板上可以插入任何實現PCI接口規範的卡 
 *  
 * @param pci 參數類型爲接口,任何實現接口的類都可以傳進來. 
 */  
public class MainBoard {
public void usePCICard(PCI pci)
{
    pci.start();
    pci.stop();
    }
}

3.2 網卡類:

/** 
 * 網卡實現PCI接口規範 
 * @author xiaodeng 
 * 
 */  
public class NetworkCard implements PCI{
    public void start()
    {
        System.out.println("send..start");
    }
    public void stop()
    {
        System.out.println("network stop");
    }
}

4、具體實現對接業務層:

public class Assembler {
      public static void main(String[] args) {  

            MainBoard mb=new MainBoard();   
            //在主板上插入網卡 

            mb.usePCICard(new NetworkCard());   
            //自己調用start()和stop()方法

           //在主板上插入聲卡  
            mb.usePCICard(new SoundCard());   

            //在主板上插入顯卡  

            mb.usePCICard(new GraphicCard());   


}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章