面向過程編程(Procedure Oriented
、簡稱PO
) 和 面向對象編程(Object Oriented
、簡稱OO
) 咱們必定聽過,然而實際企業級開發裏受用更多的一種編程思想那就是:面向接口編程(Interface-Oriented
)!java
接口這個概念咱們必定不陌生,實際生活中最多見的例子就是:插座!程序員
咱們只須要事先定義好插座的接口標準,各大插座廠商只要按這個接口標準生產,管你什麼牌子、內部什麼電路結構,這些均和用戶無關,用戶拿來就能夠用;並且即便插座壞了,只要換一個符合接口標準的新插座,一切照樣工做!編程
同理,實際代碼設計也是這樣!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
層使用接口來進行一層隔離:
這種接口定義和具體實現邏輯的分開,很是有利於後續擴展和維護!
面向接口編程開發,對代碼架構的解耦和擴展確實頗有好處,這種編碼思想也值得平時開發結合實踐反覆理解和回味!