介面和抽象類

五月的倉頡發表於2015-10-20

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

相關文章