和表妹去逛超市...
我:買,問題不大。
表妹:他家的酸奶都在搞活動,買酸奶送雞蛋。
我:那不挺好嘛?把雞蛋也一起買啦。
表妹:可是我家還有好多雞蛋......
你看,這種“捆綁式消費”,不就類似違反了我們軟體設計中的“介面隔離原則”嘛?雞蛋並不是我們想要的,但是它卻跟我們想要的酸奶綁在一起,酸奶就變得“臃腫”了。
客戶端不應該強迫依賴它不需要的介面。其中的“客戶端”,可以理解為介面的呼叫者或使用者。
如何理解“介面”?
理解“介面隔離原則”的重點是理解其中的“介面”二字。這裡有三種不同的理解:
-
如果把“介面”理解為一組介面集合,可以是某個微服務的介面,也可以是類庫的介面等。如果部分介面只被部分呼叫者使用,我們就需要將這部分介面隔離出來,單獨給這部分呼叫者使用,而不強迫其他呼叫者也依賴這部分不會被用到的介面。
-
如果把“介面”理解為單個API介面或函式,部分呼叫者只需要函式中的部分功能,那我們就需要把函式拆分成粒度更細的多個函式,讓呼叫者只依賴它需要的那個細粒度函式。
-
如果把“介面”理解為OOP中的介面,也可以理解為物件導向程式語言中的介面語法。那介面的設計要儘量單一,不要讓介面的實現類和呼叫者,依賴不需要的介面函式。
那麼,接下來,我們從物件導向程式語言中的介面語法的角度來學習一下“介面隔離原則”。
我們一起來看“美女”,一般來說,長得好看的,身材不錯的,氣質出眾的都可以成為美女。
所以,我們定義了一個IPettyGirl介面,顯示美女的這幾個特徵。PettyGirl是該介面的實現類。
1 // 美女介面 2 public interface IPettyGirl { 3 // 要有姣好的面孔 4 public void goodLooking(); 5 // 要有好身材 6 public void niceFigure(); 7 // 要有氣質 8 public void greatTemperament(); 9 } 10 11 // 介面的實現類 12 public class PettyGirl implements IPettyGirl { 13 private String name; 14 15 public PettyGirl(String name){ 16 this.name = name; 17 } 18 19 // 臉蛋漂亮 20 public void goodLooking() { 21 System.out.println(this.name + "---臉蛋很漂亮!"); 22 } 23 24 // 氣質要好 25 public void greatTemperament() { 26 System.out.println(this.name + "---氣質非常好!"); 27 } 28 29 // 身材要好 30 public void niceFigure() { 31 System.out.println(this.name + "---身材非常棒!"); 32 } 33 }
我們仔細想一下,長得好看,擁有好的身材,而且氣質還好的,當然是“美女”,甚至還是“女神”, “神仙小姐姐”,因為簡直是無可挑剔。這是所有人心目中的評判標準。
但是,我們每個人都會有自己不同的審美標準。假如,隔壁老王就認為“美女”應該是具有好看的皮囊,長得好看,身材好。老李就喜歡有趣的靈魂,認為氣質好的才是“美女”。
那麼,這樣看來,IPettyGirl介面的設計就顯得比較臃腫了,因為太寬泛了,其實每個人的審美標準都不同。所以,我們將該介面進行拆分,如下:
1 // IGoodBodyGirl 好看的皮囊 2 public interface IGoodBodyGirl { 3 // 要有姣好的面孔 4 public void goodLooking(); 5 // 要有好身材 6 public void niceFigure(); 7 } 8 9 // IGreatTemperamentGirl 有趣的靈魂 10 public interface IGreatTemperamentGirl { 11 // 要有氣質 12 public void greatTemperament(); 13 } 14 15 // PettyGirl1 老王心目中的美女---長得好看,身材好 16 public class PettyGirl1 implements IGoodBodyGirl { 17 private String name; 18 19 public PettyGirl1(String _name){ 20 this.name = _name; 21 } 22 23 // 臉蛋漂亮 24 public void goodLooking() { 25 System.out.println(this.name + "---臉蛋很漂亮!"); 26 } 27 28 // 身材要好 29 public void niceFigure() { 30 System.out.println(this.name + "---身材非常棒!"); 31 } 32 } 33 34 // PettyGirl2 老李心目中的美女---氣質好 35 public class PettyGirl2 implements IGreatTemperamentGirl { 36 private String name; 37 public PettyGirl2(String _name) { 38 this.name = _name; 39 } 40 41 // 氣質要好 42 public void greatTemperament() { 43 System.out.println(this.name + "---氣質非常好!"); 44 } 45 }
你看,這樣的設計,是不是更加靈活,易擴充套件了?
假如,有些人認為心靈美的人,才叫“美女”。我們只需擴充套件如下程式碼,而無需改動之前的程式碼。
1 // IMindBeautyGirl 心靈美 2 public interface IMindBeautyGirl { 3 // 心靈美 4 public void mindbeauty(); 5 } 6 7 // PettyGirl3 心靈美的美女 8 public class PettyGirl3 implements IMindBeautyGirl { 9 private String name; 10 public PettyGirl3(String _name) { 11 this.name = _name; 12 } 13 14 // 心靈美 15 public void mindbeauty() { 16 System.out.println(this.name + "---心靈美!"); 17 } 18 }
但是,如果按照IPettyGirl這個介面設計的話,我們不但要改動IPettyGirl這個介面,還要改每一個實現類。改不好,可能還會引入新的bug。
你看,介面粒度小,設計的改動也比較少。
介面是我們設計時對外提供的契約,通過分散定義多個介面,可以預防未來變更的擴散,提供系統的靈活性和可維護性。
不過,你可能發現,介面隔離原則跟單一職責原則有點類似。但是,還是有點區別的。
介面隔離原則與單一職責原則的區別
單一職責原則針對的是模組、類、介面的設計,注重的是職責,這是業務邏輯上的劃分。
介面隔離原則相對於單一職責原則,一方面更側重於介面的設計,另一方面,它的思考角度也是不同的。
介面隔離原則提供了一種判斷介面的職責是否單一的標準:通過呼叫者如何使用介面來間接地判定。如果呼叫者只使用部分介面或介面的部分功能,那麼介面的設計就比較“臃腫”,違背了“介面隔離原則”。
那可能有同學會問:“如果一個介面,按照介面隔離原則要拆,但是按照單一職責原則就不用拆,那應該怎麼辦呢?”
其實很好辦,根據介面隔離原則拆分時,首先必須滿足單一職責原則。
總結
一個介面只幹一件事,介面要精簡單一。
目的:高內聚、低耦合。
好啦,每個設計原則是否運用得當,需要根據具體的業務場景,具體分析。
參考
極客時間專欄《設計模式之美》
《設計模式之禪》