四、接口隔离原则
4.1、接口隔離原則的定義
在講接口隔離原則之前,我們先明確一下我們的主角,什么是接口,接口分為兩種:
一種是實例接口 (Object Interface),在 Java 中聲明一個類,然后用 new 關鍵字產生的一個實例,它是對一個類型的事 物描述,這是一種接口,比如你定義個 Person 這個類,然后使用 Person zhangSan = new Person()產生了 一個實例,這個實例要遵從的標準就是 Person 這個類,Person 類就是 zhangSan 的接口,看不懂?不要緊, 那是讓 Java 語言浸染的時間太長了。主角已經出場了,那我們看它的原則是什么,它有兩種定義:
第一種定義: Clients should not be forced to depend upon interfaces that they don't use. 客戶端不應該依賴它不需用的接口。
第二種定義:The dependency of one class to another one should depend on the smallest possible interface。類間的依賴關系應該建立在最小的接口上。
一個新事物的定義一般都是比較難理解的,晦澀難懂是正常的,否則那會讓人家覺的你沒有水平,這 也是一些國際廠商在國內忽悠的基礎,不整些名詞怎么能讓你崇拜我呢?我們把這個定義剖析一下,先說 第一種定義客戶端不應該依賴它不需要接口,那依賴什么?依賴它需要的接口,客戶端需要什么接口就提 供什么借口,把不需要的接口剔除掉,那就需要對接口進行細化,保證其純潔性;再看第二個定義,類間 的依賴關系應該建立在最小的接口上,它要求是最小的接口,也是要求接口細化,接口純潔,與第一個定 義如出一轍,只是一個事物的兩種不同描述。
我們可以把這兩個定義概括為一句話:建立單一接口,不要建立臃腫龐大的接口。再通俗的一點講: 接口盡量細化,同時接口中的方法盡量的少。看到這里大家有可能要疑惑了,這與單一職責原則不是相同 的嗎?錯,接口隔離原則與單一職責的定義的規則是不相同的,單一職責要求的是類和接口職責單一,注重的是職責,沒有要求接口的方法減少,例如一個職責可能包含 10 個方法,這 10 個方法都放在一個接口 中,并且提供給多個模塊訪問,各個模塊按照規定的權限來訪問,在系統外通過文檔約束不使用的方法不 要訪問,按照單一職責原則是允許的,按照接口隔離原則是不允許的,因為它要求“盡量使用多個專門的 接口”,專門的接口指什么?就是指提供給多個模塊的接口,提供給幾個模塊就應該有幾個接口,而不是建 立一個龐大的臃腫的接口,所有的模塊可以來訪問。
未遵循接口隔離原則的設計:
遵循接口隔離原則的設計:
?
根據接口隔離原則拆分接口時,必須首先滿足單一職責原則。
我們來舉個例子來說明接口隔離原則到底對我們提出了什么要求。現在男生對小姑娘的稱呼使用頻率 最高的應該是“美女”了吧,我們今天來定義一下什么是美女:首先要面貌好看,其次是身材要窈窕,然 后要有氣質,當然了,這三者各人的排列順序不一樣,總之要成為一名美女就必須具備:面貌、身材和氣質,我們用類圖類體現一下星探(當然,你也可以把你自己想想成星探)找美女的過程,看類圖:
定義了一個 IPettyGirl 接口,聲明所有的美女都應該有 goodLooking、niceFigure 和 greatTemperament,然后又定義了一個抽象類 AbstractSearcher,其作用就是搜索美女然后展示信息,只 要美女都是按照這個規范定義,Searcher(星探)就輕松的多了,我們先來看美女的定義:
public interface IPettyGirl { //要有姣好的面孔 public void goodLooking(); //要有好身材 public void niceFigure(); //要有氣質 public void greatTemperament(); }美女就是這樣的一個定義,各位色狼別把口水流到了鍵盤上,然后我們看美女的實現類:
public class PettyGirl implements IPettyGirl { private String name; //美女都有名字 public PettyGirl(String _name){this. name=_name;} //臉蛋漂亮 public void goodLooking() {System. out.println(this. name + "---臉蛋很漂亮!");} //氣質要好 public void greatTemperament() {System. out.println(this. name + "---氣質非常好!");} //身材要好 public void niceFigure() {System. out.println(this. name + "---身材非常棒!");} }然后我們來看 AbstractSearcher 類,這個類一般就是指星探這個行業了,源代碼如下:
public abstract class AbstractSearcher { protected IPettyGirl pettyGirl; public AbstractSearcher(IPettyGirl _pettyGirl){this.pettyGirl = _pettyGirl;} //搜索美女,列出美女信息 public abstract void show(); }場景中的兩個角色美女和星探都已經完成了,我們再來寫個場景類,展示一下我們的這個過程:
public class Client { //搜索并展示美女信息 public static void main(String[] args) {//定義一個美女IPettyGirl yanYan = new PettyGirl(" 嫣嫣");AbstractSearcher searcher = new Searcher(yanYan);searcher.show();} } 星探查找到美女,打印出美女的信息,源碼如下: public class Searcher extends AbstractSearcher{ public Searcher(IPettyGirl _pettyGirl){super(_pettyGirl);} //展示美女的信息 public void show(){System. out.println("--------美女的信息如下: ---------------");//展示面容super. pettyGirl.goodLooking();//展示身材 super. pettyGirl.niceFigure();//展示氣質super. pettyGirl.greatTemperament();} }?
運行結果如下:
--------美女的信息如下: --------------- 嫣嫣---臉蛋很漂亮! 嫣嫣---身材非常棒! 嫣嫣---氣質非常好!采用接口隔離原則:重新修改一下類圖:
?
把原 IPettyGirl 接口拆分為兩個接口,一種是外形美的美女 IGoodBodyGirl,這類美女的特點就是臉 蛋和身材極棒,超一流,但是沒有審美素質,比如隨地吐痰,出口就是 KAO,CAO 之類的,文化程度比較低; 另外一種是氣質美的美女 IGreatTemperamentGirl,談吐和修養都非常高。我們從一個比較臃腫的接口拆分 成了兩個專門的接口,靈活性提高了,可維護性也增加了,不管以后是要外形美的美女還是氣質美的美女 都可以輕松的通過 PettyGirl 定義。我們先看兩種類型的美女接口:
public interface IGoodBodyGirl { //要有姣好的面孔 public void goodLooking(); //要有好身材 public void niceFigure(); } public interface IGreatTemperamentGirl { //要有氣質 public void greatTemperament(); }實現類沒有改變,只是實現類兩個接口,源碼如下:
public class PettyGirl implements IGoodBodyGirl,IGreatTemperamentGirl { private String name; //美女都有名字 public PettyGirl(String _name){this. name=_name;} //臉蛋漂亮 public void goodLooking() {System. out.println(this. name + "---臉蛋很漂亮!");} //氣質要好 public void greatTemperament() {System. out.println(this. name + "---氣質非常好!");} //身材要好 public void niceFigure() {System. out.println(this. name + "---身材非常棒!");} }通過這樣的改造以后,不管以后是要氣質美女還是要外形美女,都可以保持接口的穩定。當然你可能 要說了,以后可能審美觀點再發生改變,只有臉蛋好看就是美女,那這個 IGoodBody 接口還是要修改的呀, 確實是,但是設計時有限度的,不能無限的考慮未來的變更情況,否則就會陷入設計的泥潭中而不能自拔。 以上把一個臃腫的接口變更為兩個獨立的接口依賴的原則就是接口隔離原則,讓 AbstractSearcher 依 賴兩個專用的接口比依賴一個綜合的接口要靈活。接口是我們設計時對外提供的契約,通過分散定義多個 接口,可以預防未來變更的擴散,提高系統的靈活性和可維護性。
4.3 保證接口的純潔性
接口隔離原則是對接口進行規范約束,其包含以下4層含義:
● 接口要盡量小
這是接口隔離原則的核心定義,不出現臃腫的接口(Fat Interface),但是“小”是有限度的,首先就是不能違反單一職責原則,什么意思呢?我們在單一職責原則中提到一個IPhone
的例子,在這里,我們使用單一職責原則把兩個職責分解到兩個接口中,類圖如下所示:
?
仔細分析一下IConnectionManager接口是否還可以再繼續拆分下去,掛電話有兩種方式:一種是正常的電話掛斷,一種是電話異常掛機,比如突然沒電了,通信當然就斷了。這
兩種方式的處理應該是不同的,為什么呢?正常掛電話,對方接受到掛機信號,計費系統也就停止計費了,那手機沒電了這種方式就不同了,它是信號丟失了,中繼服務器檢查到了,
然后通知計費系統停止計費,否則你的費用不是要瘋狂地增長了嗎?
思考到這里,我們是不是就要動手把IConnectionManager接口拆封成兩個,一個接口是負責連接,一個接口是負責掛電話?是要這樣做嗎?且慢,讓我們再思考一下,如果拆分
了,那就不符合單一職責原則了,因為從業務邏輯上來講,通信的建立和關閉已經是最小的業務單位了,再細分下去就是對業務或是協議(其他業務邏輯)的拆分了。想想看,一個電
話要關心3G協議,要考慮中繼服務器,等等,這個電話還怎么設計得出來呢?從業務層次來看,這樣的設計就是一個失敗的設計。一個原則要拆,一個原則又不要拆,那該怎么辦?
好辦,根據接口隔離原則拆分接口時,首先必須滿足單一職責原則。
?● 接口要高內聚
什么是高內聚?高內聚就是提高接口、類、模塊的處理能力,減少對外的交互。比如你告訴下屬“到奧巴馬的辦公室偷一個×××文件”,然后聽到下屬用堅定的口吻回答你:“是,保
證完成任務!”一個月后,你的下屬還真的把×××文件放到你的辦公桌上了,這種不講任何條件、立刻完成任務的行為就是高內聚的表現。具體到接口隔離原則就是,要求在接口中盡量少公布public方法,接口是對外的承諾,承諾越少對系統的開發越有利,變更的風險也就越少,同時也有利于降低成本。
?● 定制服務
一個系統或系統內的模塊之間必然會有耦合,有耦合就要有相互訪問的接口(并不一定就是Java中定義的Interface,也可能是一個類或單純的數據交換),我們設計時就需要為各
個訪問者(即客戶端)定制服務,什么是定制服務?定制服務就是單獨為一個個體提供優良的服務。我們在做系統設計時也需要考慮對系統之間或模塊之間的接口采用定制服務。采用
定制服務就必然有一個要求:只提供訪問者需要的方法,這是什么意思?我們舉個例子來說明,比如我們開發了一個圖書管理系統,其中有一個查詢接口,方便管理員查詢圖書,其類
圖如下圖所示:
在接口中定義了多個查詢方法,分別可以按照作者、標題、出版社、分類進行查詢,最后還提供了混合查詢方式。程序寫好了,投產上線了,突然有一天發現系統速度非常慢,然
后就開始痛苦地分析,最終發現是訪問接口中的complexSearch(Map map)方法并發量太大,導致應用服務器性能下降,然后繼續跟蹤下去發現這些查詢都是從公網上發起的,進一步分析,找到問題:提供給公網(公網項目是另外一個項目組開發的)的查詢接口和提供給系統內管理人員的接口是相同的,都是IBookSearcher接口,但是權限不同,系統管理人員可以通過接口的complexSearch方法查詢到所有的書籍,而公網的這個方法是被限制的,不返回任何值,在設計時通過口頭約束,這個方法是不可被調用的,但是由于公網項目組的疏忽,這個方法還是公布了出去,雖然不能返回結果,但是還是引起了應用服務器的性能巨慢的情況發生,這就是一個臃腫接口引起性能故障的案例。
問題找到了,就需要把這個接口進行重構,將IBookSearcher拆分為兩個接口,分別為兩個模塊提供定制服務,修改后的類圖如下所示:
?
提供給管理人員的實現類同時實現了ISimpleBookSearcher和IComplexBookSearcher兩個接口,原有程序不用做任何改變,而提供給公網的接口變為ISimpleBookSearcher,只允許進行簡單的查詢,單獨為其定制服務,減少可能引起的風險。
● 接口設計是有限度的?
接口的設計粒度越小,系統越靈活,這是不爭的事實。但是,靈活的同時也帶來了結構的復雜化,開發難度增加,可維護性降低,這不是一個項目或產品所期望看到的,所以接口
設計一定要注意適度,這個“度”如何來判斷呢?根據經驗和常識判斷,沒有一個固化或可測量的標準。
?
最佳實踐
接口隔離原則是對接口的定義,同時也是對類的定義,接口和類盡量使用原子接口或原子類來組裝。但是,這個原子該怎么劃分是設計模式中的一大難題,在實踐中可以根據以下幾個規則來衡量:
● 一個接口只服務于一個子模塊或業務邏輯;
● 通過業務邏輯壓縮接口中的public方法,接口時常去回顧,盡量讓接口達到“滿身筋骨? ?肉”,而不是“肥嘟嘟”的一大堆方法;
● 已經被污染了的接口,盡量去修改,若變更的風險較大,則采用適配器模式進行轉化處理;
● 了解環境,拒絕盲從。每個項目或產品都有特定的環境因素,別看到大師是這樣做的你就照抄。千萬別,環境不同,接口拆分的標準就不同。深入了解業務邏輯,最好的接口設計就 ? ?出自你的手中!
接口隔離原則和其他設計原則一樣,都需要花費較多的時間和精力來進行設計和籌劃,但是它帶來了設計的靈活性,讓你可以在業務人員提出“無理”要求時輕松應付。貫徹使用接
口隔離原則最好的方法就是一個接口一個方法,保證絕對符合接口隔離原則(有可能不符合單一職責原則),但你會采用嗎?不會,除非你是瘋子!那怎么才能正確地使用接口隔離原
則呢?答案是根據經驗和常識決定接口的粒度大小,接口粒度太小,導致接口數據劇增,開發人員嗆死在接口的海洋里;接口粒度太大,靈活性降低,無法提供定制服務,給整體項目
帶來無法預料的風險。
怎么準確地實踐接口隔離原則?實踐、經驗和領悟!
轉載于:https://www.cnblogs.com/duanxz/archive/2012/10/17/2727916.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
- 上一篇: 一位ACMer过来人的心得【转】
- 下一篇: AngularJs学习笔记--unit-