目錄結構
- 前言
- 什麼是介面?
- 如何宣告介面?
- 介面的作用?
- 什麼時候可以考慮使用介面?
- 什麼時候可以不考慮使用介面?
- 線上介面文件化平臺
- 總結
前言
今天有個同事問我,如何理解介面,剛好中午下班要去吃飯,邊走邊說,但是都沒有說到重點,所以想通過這篇文章來分享一下自己對介面的理解,閱讀需要2分鐘。
什麼是介面?
物件通過它們公開的方法來定義它們與外界的互動行為,而方法就形成了與外界互動的介面。例如電視機的開關按鈕就是你與塑料外殼另一側的電線之間的介面,你只要按下電源開關這個按鈕,就可以控制電視機開機和關機,而你不需要關注電視機開機和關機的細節,介面就是讓你知道它在做什麼,而無需知道它們怎麼做;介面更深層的理解是:使定義(規範和約束)和實現(具體的程式碼邏輯)分離,它是溝通(互動)的中介物(具體實現)的抽象化。
如何宣告介面?
public inteface TV {
// 開機
public void open();
// 關機
public void close();
// 選擇頻道
public void selectChannel(Integer channel);
// 設定聲音大小
public void setVolume(Integer vlume);
...
}
複製程式碼
上面宣告瞭電視機的介面(程式語言層面的介面),暴露4個方法(與外界互動的介面),通過閱讀這些方法,你腦海裡大致可以對這個電視機建模,並且知道這個電視機可以做些什麼,這樣大家就都達成了一個共識,電視機都會具有哪些功能了;而具體要怎麼實現電視機的那些功能(開機、關機、選頻道、設定聲音大小)呢?這就交給不同的電視機廠商吧。
// 小米電視
public inteface MiTV extends TV {
// 擴充套件玩遊戲介面
public void playGame(String gameId);
}
public class MiTVImpl implements MiTV {
@Overide
pubilc void open() {
// TODO 小米對開機的實現
}
@Overide
public void close() {
// TODO 小米對關機的實現
}
@Overide
public void selectChannel(Integer channel) {
// TODO 小米對選擇頻道的實現
}
@Overide
public void setVolume() {
// TODO 小米對聲音大小控制的實現
}
// 擴充套件玩遊戲介面
@Overide
public void playGame(String gameId) {
// TODO 對遊戲的實現
}
}
複製程式碼
從上面的程式碼中可以知道,小米電視機不僅實現了電視機的基本操作,自身還擴充套件了玩遊戲的介面,使用者只需要通過選擇小米電視提供的遊戲,就可以玩遊戲啦。
介面的作用?
- 介面即是設計:在設計層面,介面可以避免我們陷入對細節的過多思考,可以讓我們站在一個更高的視角對系統做更好的評估,比如系統的互動設計是否合理,功能是否缺失,是否具備可行性,是否過於複雜等等。
- 介面即是約定:在編碼層面,介面可以明確的告訴開發人員如何使用(介面的語義,需要什麼作為輸入,會有什麼輸出),而開發人員只需要根據這些約定去實現具體的功能程式碼即可。
- 統一類的共同行為:介面用來統一類的共通行為,當不同的類需要進行資訊共享時,是不需要特別去建立類間的關係。舉例來說,一個人(Human)及一隻鸚鵡(Parrot)都會吹口哨(whistle),然而 Human 及 Parrot 不應該為 Whistler 的子類,最好的做法是令他們為 Animal 的子類,而他們可以使用 Whistler 的介面進行溝通。
// 吹口哨
public inteface Whistler {
/**
* 吹口哨
*/
public void whistle();
}
public class JuniorWhistler implements Whistler {
public void whistle() {
System.out.println("入門級口哨聲");
}
}
public class SeniorWhistler implements Whistler {
public void whistle() {
System.out.println("高階口哨聲");
}
}
// 定義動物介面
public inteface Animal {
/**
* 吹口哨
*
* @param whistle
* @return
*/
public void whistle(Whistler whistle);
}
public class Human implements Animal {
public void whistle(Whistler whistle) {
whistle.whistle();
}
...
}
public class Parrot implements Animal {
public void whistle(Whistler whistle) {
whistle.whistle();
}
...
}
public class Demo {
public static void main(String[] args) {
// 人吹口哨
Human human = new Human();
human.whistle(new JuniorWhistler()); // 入門級口哨聲
// 鸚鵡口哨
Human human = new Human();
human.whistle(new SeniorWhistler()); // 高階口哨聲
}
}
複製程式碼
- 使用時無需知道實現類:當介面有實現類時,在使用它的時候無需知道它的實現類是什麼(感興趣的可以瞭解一下多型、依賴注入)。例如,一個事物因為口哨的噪音影響到其他人,對於其他人而言,就不需要知道噪音來源是來自人還是鸚鵡,因為他們可以確定,一個會吹口哨的事物正在吹口哨。舉一個更實際的例子,排序演算法可能會期待物件的型別是可以被比較的,於是它只需要知道物件的型別可以被以某種方式進行排序即可,這與物件的型別無關。whistler.whistle() 將會呼叫物件的實現方法 whistle,而不需要知道物件是以哪個類來實現 Whistler。
public inteface Whistler {
public void whistle();
}
public class Human implements Animal {
public void whistle(Whistler whistle) {
whistle.whistle();
}
...
}
複製程式碼
Human
類中的 whistle()
方法的實現 whistle.whistle()
不需要知道口哨具體的實現類是哪個,而 whistle(Whistler whistle)
方法 只關注入參是 Whistler 型別就行。
至於 Java 如何實現動態繫結到具體的實現類上的方法,這個之後另開一篇文章來寫。
什麼時候可以考慮使用介面?
這雖然很難去定義(即使很多人一直使用面向介面程式設計),但個人還是根據自身的開發經驗淺談一下,如果說的不對,歡迎大家指教。
- 當專案沒有良好的開發規範、API文件還沒出、專案緊,當專案組有不少新人多時,需要使用介面,而這個介面的定義應該由有經驗、對業務和專案較為了解的人去定義,這樣子就可以嚴格約定好介面的輸入輸出、介面命名、引數命名,而不會被亂來,而新人只需要寫對應的實現就可以,這樣子在重構的時候,不會導致由於各種奇奇怪怪的問題而去改介面,一旦改了介面,出問題的可能性會更大(這個坑踩過,痛過,特別是沒有單元測試來做迴歸測試的時候)。
- 當需要使用到策略模式的時候,應該基於介面來實現,比如不同國家的貨幣換算等。
- 當框架功能對於系統基於介面設計的擴充套件非常友好時,應該使用介面,比如依賴注入,這個時候,介面可以使系統更具擴充套件性,更符合Open Close原則。
- 基於SOA理念,暴露出來的服務必須是介面——阿里的Java開發規範手冊裡面就有嚴格說到這點。
什麼時候可以不考慮使用介面?
- 小專案,參與的人數較少時,時間緊,可以考慮不需要使用到介面,因為介面本身就不多,無需增加各種文件化的工作量。
線上介面文件化平臺
推薦的一些線上API管理平臺 Swagger Editor、eolinker
- swagger 偏向開發人員,需要掌握編寫swagger文件的一些語法,上手稍慢,但介面好看,功能也強大。
- eolinker 全部都是圖形化,上手快,功能夠用。
總結
本文通過一些例子和個人的開發經驗,從介面的定義、介面的宣告、再到介面的適用場景,來與讀者分享本人對介面的理解,希望可以給讀者一些收穫,如果發現本人有理解不對的地方,或者有需要補充的地方,歡迎評論交流。