接口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的功能,那么上述的定義方式就要反過來了。
