為什麼我們要面向接口編程?

為什麼我們要面向接口編程?

到底面向?編程

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

接口這個概念我們一定不陌生,實際生活中最常見的例子就是:插座!

我們只需要事先定義好插座的接口標準,各大插座廠商只要按這個接口標準生產,管你什麼牌子、內部什麼電路結構,這些均和用戶無關,用戶拿來就可以用;而且即使插座壞了,只要換一個符合接口標準的新插座,一切照樣工作!

為什麼我們要面向接口編程?

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

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

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

所以接下來落實到代碼上,舉個通俗一點的小例子嘮一嘮,實際業務代碼雖然比這個複雜,但原理是一模一樣的。


做夢了

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

兩輛豪車在此:

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

public class Fit {
public void drive() {
System.out.println("駕駛飛度汽車");
}
}/<code>

駕駛員定義在此:

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

<code>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 ); // 幫我開飛度
}

}/<code>

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

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

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


代碼的靈活解耦

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

這時候如果我希望我聘請的這位駕駛員對於所有車型都能駕馭,該怎麼辦呢?

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

<code>// 抽象的汽車類Car,代表所有汽車
public class Car {
void drive() { } // 通用的汽車駕駛方法

}/<code>

這時,只要我新買的奧拓符合Car定義的駕駛標準即可被我的駕駛員駕駛,所以只需要新的奧拓來繼承一下Car類即可:

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

同理,只需要我的駕駛員具備通用汽車Car的駕駛能力,那駕駛所有的汽車都不是問題,因此Drvier類的drive()方法只要傳入的參數是父類,那就具備了通用性:

<code>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 );
}
}/<code>

問題暫且解決了!


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

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

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

<code>// 通用的交通工具接口定義
public interface TrafficTools {
void drive(); // 通用的交通工具使用方法
}/<code>

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

<code>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("騎一頭驢");
}
}/<code>

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

<code>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() ); // 騎一頭驢
}
}/<code>

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


代碼的擴展性

面向接口編程的優點遠不止上面這種代碼解耦的場景,在實際企業開發裡,利用接口思想

對已有代碼進行靈活擴展也特別常見。

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

<code>// 通用的飛機飛行接口
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 ); // 由專業機長開飛機愉快的出去旅行
}
}/<code>

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

於是他跑來問我借司機,想讓我的駕駛員來幫他駕駛飛機出去旅行。

我一看,由於他們的代碼面向的是接口,我就肯定地答應了他!

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

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

public void drive( TrafficTools trafficTools ) {
trafficTools.drive();
}

// 實現了fly()方法,這下我的駕駛員也具備操控飛機的能力了!
@Override
public void fly() {
System.out.println("普通駕駛員操控飛機");
}
}/<code>

這時候我的駕駛員Driver類就可以直接服務於他們一家的出行了:

<code>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 );
}
}/<code>

看到沒,這一改造過程中,我們只增加了代碼,卻並沒有修改任何已有代碼,就完成了代碼擴展的任務,非常符合開閉原則


實際項目

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

為什麼我們要面向接口編程?

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


小結

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

文章來源:https://segmentfault.com/a/1190000021928946

關注我瞭解更多程序員資訊技術,領取豐富架構資料


分享到:


相關文章: