接口和抽象類


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM