爲何咱們要面向接口編程?!

image


到底面向?編程

面向過程編程(Procedure Oriented、簡稱PO面向對象編程(Object Oriented、簡稱OO 咱們必定聽過,然而實際企業級開發裏受用更多的一種編程思想那就是:面向接口編程(Interface-Orientedjava

接口這個概念咱們必定不陌生,實際生活中最多見的例子就是:插座!程序員

咱們只須要事先定義好插座的接口標準,各大插座廠商只要按這個接口標準生產,管你什麼牌子、內部什麼電路結構,這些均和用戶無關,用戶拿來就能夠用;並且即便插座壞了,只要換一個符合接口標準的新插座,一切照樣工做!編程

image

同理,實際代碼設計也是這樣!bash

咱們在設計一個軟件的代碼架構時,咱們都但願事先約定好各個功能的接口(即:約定好接口簽名和方法),實際開發時咱們只須要實現這個接口就能完成具體的功能!後續即便項目變化、功能升級,程序員只須要按照接口約定從新實現一下,就能夠達到系統升級和擴展的目的!架構

正好,Java中天生就有interface這個語法,這簡直是爲面向接口編程而生的!框架

因此接下來落實到代碼上,舉個通俗一點的小例子嘮一嘮,實際業務代碼雖然比這個複雜,但原理是如出一轍的。ide


作夢了

假如哪一天程序羊真發達了,一口豪氣買了兩輛豪車,一輛五菱宏光、一輛飛度、而且還專門聘請了一位駕駛員來幫助駕駛。函數

兩輛豪車在此:工具

public class Wuling {
    public void drive() {
        System.out.println("駕駛五菱宏光汽車");
    }
}

public class Fit {
    public void drive() {
        System.out.println("駕駛飛度汽車");
    }
}
複製代碼

駕駛員定義在此:測試

駕駛員定義了兩個drive()方法,分別用來駕駛兩輛車:

public class Driver {

    public void drive( Wuling wuling ) {
        wuling.drive(); // 駕駛五菱宏光的方法
    }
    
    public void drive( Fit fit ) {
        fit.drive();    // 駕駛飛度的方法
    }

    // 用於測試功能的 main()函數
    public static void main( String[] args ) {
    
        // 實例化兩輛新車
        Wuling wuling = new Wuling();
        Fit fit = new Fit();
        
        // 實例化駕駛員
        Driver driver = new Driver();
        
        driver.drive( wuling ); // 幫我開五菱宏光
        driver.drive( fit );    // 幫我開飛度
    }
}
複製代碼

這暫且看起來沒問題!日子過得很融洽。

但後來過了段時間,程序羊又變得發達了一點,此次他又豪氣地買了一輛新款奧拓(Alto)!

但是現有的駕駛員類Driver的兩個drive()方法裏都開不了這輛新買的奧拓該怎麼辦呢?


代碼的靈活解耦

這時候,我想應該沒有誰會專門再去往Driver類中添加一個新的drive()方法來達到目的吧?畢竟誰也不知道之後他還會不會買新車!

這時候若是我但願我聘請的這位駕駛員對於全部車型都能駕馭,該怎麼辦呢?

很容易想到,咱們應該作一層抽象。畢竟不論是奧拓仍是奧迪,它們都是汽車,所以咱們定義一個父類叫作汽車類Car,裏面只聲明一個通用的drive()方法,具體怎麼開先不用管:

// 抽象的汽車類Car,表明全部汽車
public class Car {
    void drive() { } // 通用的汽車駕駛方法
}
複製代碼

這時,只要我新買的奧拓符合Car定義的駕駛標準便可被個人駕駛員駕駛,因此只須要新的奧拓來繼承一下Car類便可:

public class Alto extends Car {
    public void drive() {
        System.out.println("駕駛奧拓汽車");
    }
}
複製代碼

同理,只須要個人駕駛員具有通用汽車Car的駕駛能力,那駕駛全部的汽車都不是問題,所以Drvier類的drive()方法只要傳入的參數是父類,那就具有了通用性:

public class Driver {

    public void drive( Car car ) {  // 方法參數使用父類來替代
        car.drive();
    }

    public static void main( String[] args ) {
        Alto alto = new Alto();
        Driver driver = new Driver();
        driver.drive( alto );
    }
}
複製代碼

問題暫且解決了!


可是再後來,程序羊他好像又更發達了一些,連車都不想坐了,想買一頭驢(Donkey)讓司機騎着帶他出行!

很明顯,原先適用於汽車的drive()方法確定是不適合騎驢的!但咱們但願聘請的這位駕駛員既會開汽車,又會騎驢怎麼辦呢?

害!咱們乾脆直接定義一個叫作交通工具(TrafficTools)的通用接口吧!裏面包含一個通用的交通工具使用方法,管你是駕駛汽車,仍是騎驢騎馬,具體技能怎麼實現先無論:

// 通用的交通工具接口定義
public interface TrafficTools {
    void drive();  // 通用的交通工具使用方法
}
複製代碼

有了這個接口約定,接下來就好辦了。咱們讓全部的Car、或者驢、馬等,都來實現這個接口:

public class Car implements TrafficTools {
    @Override
    public void drive() { }
}

public class Wuling extends Car {
    public void drive() {
        System.out.println("駕駛五菱宏光汽車");
    }
}

public class Fit extends Car {
    public void drive() {
        System.out.println("駕駛飛度汽車");
    }
}

public class Alto extends Car {
    public void drive() {
        System.out.println("駕駛奧拓汽車");
    }
}

public class Donkey implements TrafficTools {
    @Override
    public void drive() {
        System.out.println("騎一頭驢");
    }
}

複製代碼

這個時候只要咱們的駕駛員師傅也面向接口編程,就沒有任何問題:

public class Driver {

    // 方法參數面向接口編程
    public void drive( TrafficTools trafficTools ) {
        trafficTools.drive();
    }

    public static void main( String[] args ) {
        Driver driver = new Driver();
        driver.drive( new Wuling() );  // 開五菱
        driver.drive( new Fit() );     // 開飛度
        driver.drive( new Alto() );    // 開奧拓
        driver.drive( new Donkey() );  // 騎一頭驢
    }
}
複製代碼

很明顯,代碼徹底解耦了!這就是接口帶來的便利。


代碼的擴展性

面向接口編程的優勢遠不止上面這種代碼解耦的場景,在實際企業開發裏,利用接口思想對已有代碼進行靈活擴展也特別常見。

再舉一個例子:假設程序羊有一個很是豪氣的朋友,叫:程序牛,他們家出行可不坐車,全靠私人飛機出行:

// 通用的飛機飛行接口
public interface Plane {
    void fly();
}

// 程序牛的專用機長,受過專業訓練(即:實現了通用飛行接口)
public class PlaneDriver implements Plane {
    @Override
    public void fly() {
        System.out.println("專業的飛行員操控飛機");
    }
}

// 出門旅行
public class Travel {

    // 此處函數參數也是面向接口編程!!!
    public void fly( Plane plane ) {
        plane.fly();
    }

    public static void main( String[] args ) {
        Travel travel = new Travel();  // 開啓一段旅行
        PlaneDriver planeDriver = new PlaneDriver(); // 聘請一個機長
        travel.fly( planeDriver ); // 由專業機長開飛機愉快的出去旅行
    }
}

複製代碼

可是忽然有一天,他們家聘請的機長跳槽了,這時候程序牛一家就沒法出行了,畢竟飛機不會駕駛。

因而他跑來問我借司機,想讓個人駕駛員來幫他駕駛飛機出去旅行。

我一看,因爲他們的代碼面向的是接口,我就確定地答應了他!

這時候對我這邊的擴展來講就很是容易了,我只須要安排個人駕駛員去培訓一下飛行技能就OK了(實現一個方法就行):

// 讓個人駕駛員去培訓一下飛行技能(即:去實現通用飛行接口)
public class Driver implements Plane {

    public void drive( TrafficTools trafficTools ) {
        trafficTools.drive();
    }
    
    // 實現了fly()方法,這下個人駕駛員也具有操控飛機的能力了!
    @Override
    public void fly() {
        System.out.println("普通駕駛員操控飛機");
    }
}
複製代碼

這時候個人駕駛員Driver類就能夠直接服務於他們一家的出行了:

public class Travel {

    public void fly( Plane plane ) {
        plane.fly();
    }

    public static void main( String[] args ) {
        Travel travel = new Travel();

        // 專業飛行員操控飛機
        PlaneDriver planeDriver = new PlaneDriver();
        travel.fly( planeDriver );

        // 普通駕駛員操控飛機
        Driver driver = new Driver();
        travel.fly( driver );
    }
}
複製代碼

看到沒,這一改造過程當中,咱們只增長了代碼,卻並無修改任何已有代碼,就完成了代碼擴展的任務,很是符合開閉原則


實際項目

實際開發中,咱們就暫且不說諸如Spring這種框架內部會大量使用接口,並對外提供使用,就連咱們本身平時寫業務代碼,咱們也習慣於在Service層使用接口來進行一層隔離:

image

這種接口定義和具體實現邏輯的分開,很是有利於後續擴展和維護!


小結

面向接口編程開發,對代碼架構的解耦和擴展確實頗有好處,這種編碼思想也值得平時開發結合實踐反覆理解和回味!

相關文章
相關標籤/搜索