介面interface的作用
介面是對於行為的抽象,在小專案、小設計中介面帶來的好處可能不會特別明顯,但是專案一旦龐大起來,介面的優勢就很明顯會體現出來了:
1、對於一個龐大的專案,從設計的角度來說,介面的存在可以幫助理清楚業務,利用介面不僅可以告訴開發人員需要實現哪些業務,而且也將命名規範限制住了,從而防止開發人員隨便命名導致專案組別的開發者無法看明白:
public interface StudentOperate { void addStudent(String name, int age, String phone); boolean deleteStudent(int id); void qureyStudent(); boolean updateStudent(int id, String name, int age, String phone); }
這麼定義介面,意味著這個介面必須按照這個規範來,介面乾什麼用的,介面裡面有哪些功能,事先已經理清楚業務,只是沒有實現罷了。不管是專案內呼叫,還是分散式系統的分層呼叫,都按照指定的介面名、介面請求、介面響應來,相當於是定了一個規範。
2、彌補了Java類單繼承的不足,這個很好理解,類的多實現:
public class MultiClass<E> implements Cloneable, Serializable, Iterator<E> { private static final long serialVersionUID = 1L; public boolean hasNext() { return false; } public E next() { return null; } public void remove() { } }
同樣,介面也可以多繼承:
public interface MultiInterface<E> extends Cloneable, Serializable, Iterator<E> { }
這樣,實現類就是所有父介面的子類。
3、降低程式碼的耦合性。由於Java多型的特性,介面的引用是可以接受子類物件的,用實現的子類例項化宣告的介面後,就可以通過介面呼叫子類重寫的方法。也就是說呼叫介面的地方,和實現介面的地方是無關的,增加或者刪除了介面,都不需要去改動呼叫介面的地方,這就大大縮減了程式碼量、增加了程式碼的擴充套件性、靈活性,比如沒有介面的時候我們是這麼寫的:
public class ClassA { public void print() { }
public class ClassB { public void print() { } }
public class Invoke { public void print(ClassA ca) { ca.print(); } public void print(ClassB cb) { cb.print(); } }
這在類多了之後是非常麻煩的,invoke類將過載更多的print方法,但是用介面就方便多了:
public interface Print { void print(); }
public class ClassA implements Print { public void print() { } }
public class ClassB implements Print { public void print() { } }
public class Invoke { public void print(Print p) { p.print(); } }
這樣就方便多了,不管Print的子類如何增加,呼叫的地方Invoke的print方法都不需要作任何的改動,這就是解耦,這也就是介面的作用。當然,有人可能會說,繼承一個普通類或者繼承一個抽象類也有這種功能嘛,對的,但是:
(1)繼承的關係太死了,並不靈活
(2)類可以實現多個介面,但卻只能繼承一個類
(3)介面屬於一種完全的抽象,不管是繼承普通類還是繼承抽象類都達不到這種特性
介面和抽象類的區別
1、介面和抽象類的概念是不一樣的。介面是對動作的抽象,表示的是這個物件能做什麼,比如人可以吃東西、狗也可以吃東西,只要有相同的行為;抽象類是對根源的抽象,表示的是這個物件是什麼,比如男人是人、女人也是人
2、可以實現多個介面,只能繼承一個抽象類
3、介面中只能定義抽象方法,抽象類中可以有普通方法
4、介面中只能有靜態的不能被改變的資料成員,抽象類可以有普通的資料成員
以上是從語法和程式設計的角度來看介面和抽象類的區別的,下面從設計理念的角度來分析一下二者個區別。考慮這樣一個例子,假設在我們的問題領域中有一個關於Door的抽象概念,該Door具有兩個動作open和close,此時我們可以通過abstract class或者interface來定義一個表示抽象概念的型別,定義方式分別為:
abstract class Door { abstract void open(); abstract void close(); }
interface Door { void open(); void close(); }
具體的Door型別可以繼承abstract class或者實現interface。這樣看起來好像使用abstract class和interface沒有大區別。但是如果現在要求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(){...} }
不得不說這是一種糟糕的方案,因為這種方案違反了物件導向設計中的一個核心原則ISP,在Door的定義中把Door概念本身固有的行為方法和另外一個概念"報警器"的行為方法混在了一起,這樣引起的一個問題是那些僅僅依賴於Door這個概念的模組會因為"報警器"這個概念的改變而改變(比如修改alarm方法的引數),反之亦然。
方案二
既然open、close和alarm屬於兩個不同的概念,根據ISP原則應該把它們分別定義在代表這兩個概念的抽象類中。定義方式有:
1、這兩個概念都用abstract class定義
2、這兩個概念都用interface定義
3、一個概念用abstract class定義、一個概念用interface定義
顯然,由於Java不支援多重繼承,所以兩個概念都用abstract class方式定義是不可行的。可能有人會說哦,讓門的抽象方法A繼承報警的抽象方法B,類再繼承抽象方法A不就好了。問題是如果我有另外一個門,有防水功能怎麼辦?抽象方法A還能繼承報警的抽象方法D嗎?所以一開頭就說了,繼承的方式太死了,是什麼就是什麼。介面就不同了,由於類可以實現多個介面,所以可以任意組合。
講完了第一種方式,再講後面兩種方式。後面兩種方式都是可行的,但對於它們的選擇卻反映出對於問題領域的概念本質的理解、對於設計意圖的反應是否正確、合理。
如果兩個概念都用interface方式定義,那麼就反映出兩個問題:
1、我們可能沒有理解清楚問題領域,AlarmDoor在概念本質上到底是Door還是報警器?
2、如果我們對於問題領域的理解沒有問題,那麼我們在實現時就沒有能夠正確地揭示我們的設計意圖,因為這兩個概念的定義上反映不出上述含義
如果我們對於問題領域的理解是:AlarmDoor在本質上是Door,同時它具有報警功能,那麼我們該如何來設計和實現來明確反映出我們的意圖?前面已經說過,abstract class在Java語言中表示一種繼承關係,而繼承關係在本質上是"is a"的關係。所以對於Door這個概念,我們應該使用abstract 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(){...} }
這種實現方式基本上能夠明確反映出我們對於問題領域的理解,正確地解釋我們的設計意圖:AlaramDoor是Door並且它有Alarm的功能。當然這是建立在對問題領域的理解上的,比如:如果我們認為AlarmDoor在概念本質上是報警器(比如說除了AlarmDoor,還有AlarmChair、AlaramDesk、AlarmBike什麼的),同時又具有Door的功能,那麼上述的定義方式就要反過來了。