關於面向接口編程,你真的弄懂了嗎?

什麼是接口(What)

百度說:接口泛指實體把自己提供給外界的一種抽象化物,用以由內部操作分離出外部溝通方法,使其能被內部修改而不影響外界其他實體與其交互的方式。

牛津字典說:Interface: A point where two systems, subjects, organizations, etc. meet and interact.

維基百科說:In computing, an interface is a shared boundary across which two or more separate components of a computer system exchange information.

Frank說:上面說的都對,但是關於接口我還有更多想說的。

接口有什麼好處(Why)

在我看來,接口在軟件設計中主要有兩大好處:

1. 制定標準

標準規範的制定離不開接口,制定標準的目的就是爲了讓定義和實現分離,而接口作爲完全的抽象,是標準制定的不二之選。

這個世界的運轉離不開分工協作,而分工協作的前提就是標準化。試想一下,你家的電腦能允許你把顯卡從NVIDIA換成七彩虹;你家的燈泡壞了,你可以隨便找一個超市買一個新的就可以換上;你把數據從Oracle換成了MySQL,但是你基於JDBC寫的代碼都不用動。等等這些事情的背後都是因爲接口,以及基於接口定製的標準化在起作用。

在Java的世界裏,有一個很NB的社區叫JCP( Java Community Process),就是專門通過JSR(Java Specification Request)來制定標準的。正是有了JSR-315(Java Servlet),我們服務端的代碼才能在Tomcat和Jetty之間自由切換。
image.png

最後,我想用一句話來總結一下標準的重要性,那就是:“一流的企業做標準,二流的企業做品牌,三流的企業做產品。

2. 提供抽象

除了標準之外,接口還有一個特徵就是抽象。正是這樣的抽象,得以讓接口的調用者和實現者可以完全的解耦。

解耦的好處是調用者不需要依賴具體的實現,這樣也就不用關心實現的細節。這樣,不管是實現細節的改動,還是替換新的實現,對於調用者來說都是透明的。

這種擴展性和靈活性,是軟件設計中,最美妙的設計藝術之一。一旦你品嚐過這種“依賴接口”的設計來帶的美好,就不大會再願意回到“依賴實現”的簡單粗暴。平時我們說的“面向接口編程原則”和“依賴倒置原則”說的都是這種設計

另外,一旦你融會貫通的掌握了這個強大的技巧——面向抽象、面向接口,你會發現,雖然面向實現和麪向接口在代碼層面的差異不大,但是其背後所隱含的設計思想和設計理念的差異,不亞於我籃球水平和詹姆斯籃球水平之間的差異!

    //面向接口
    Animal dog = new Dog();
    
    //面向實現
    Dog dog = new Dog();

作爲一名資深職場老兵,我牆裂建議各位在做系統設計、模塊設計、甚至對象設計的時候。要多考慮考慮更高層次的抽象——也就是接口,而不是一上來就陷入到實現的細節中去。要清楚的意識到接口設計是我們系統設計中的主要工作內容。而這種可以跳出細節內容,站在更高抽象層次上,來看整個系統的模塊設計、模塊劃分、模塊交互的人,正是我們軟件設計隊伍中,非常稀缺的人才。有時候,我們也管這些人叫架構師

什麼時候要用接口(When)

有擴展性需求的時候

可擴展設計,主要是利用了面向對象的多態特性,所以這裏的接口是一個廣義的概念,如果用編程語言的術語來說,它既可以是Interface,也可能是Abstract Class。

這種擴展性的訴求在軟件工作中可以說無處不在,小到一個工具類。例如,我現在系統中需要一個開關的功能,開關的配置目前是用數據庫做配置的,但是後續可能會遷移到Diamond配置中心,或者SwitchCenter上去。

簡單做法就是,我直接用數據庫的配置去實現開關功能,如下圖所示:
image.png

但是這樣做的問題很明顯,當需要切換新的配置實現的話,就不得不扒開原來的應用代碼做修改了。更恰當的做法應該是提供一個Switch的接口,讓不同的實現去實現這個接口,從而在切換配置實現的時候,應用代碼不再需要更改了。
image.png

如果說,上面的重構只是使用策略模式對代碼進行了局部優化,做了當然更好,不做的話,影響也還好,可以將就着過。

那麼接下來我要給大家介紹的場景,就不僅僅是“要不要”的問題,而是“不得不”的問題了。

例如,老闆給你佈置了一個任務,實現一個類似於eclipse可以可插拔(Pluggable)的產品,此時,使用接口就不僅僅是一個選擇問題了,而是你不得不使用的架構方法了。因爲,可插拔的本質就是,你制定一個標準接口(API),然後有不同的實現者去做插件的實現,最後再由PluginManager把這個插件機制串起來而已。

下圖是我當時給ICBU設計的一個企業協同雲的Pluggable架構,其本質上,也就是基於接口的一種標準和擴展的設計。
image.png

需要解耦的時候

上面介紹的關於Switch的例子,從表面上來看,是擴展性的訴求。但不可擴展的本質原因正是因爲耦合性。當我們通過Switch Interface來解開耦合之後,擴展性的訴求也就迎刃而解了。

發現這種耦合性,對系統的可維護性至關重要。有一些耦合比較明顯(比如Switch的例子)。但更多的耦合是隱式的,並沒有那麼明顯,而且在很長一段時間,它也不是什麼問題,但是,一旦它變成一個問題,將是一個非常頭痛的問題。

一個真實的典型案例,就是java的logger,早些年,大家使用commons-logging、log4j並沒有什麼問題。然而,此處一個隱患正在生長——那就是對logger實現的強耦合。

當logback出來之後,事情開始變得複雜,當我們想替換一個新的logger vendor的時候,爲了儘量減少代碼改動,不得不上各種Bridge(橋接),到最後日誌代碼變成了誰也看不懂的代碼迷宮。下圖就是我費了九頭二虎之力,才梳理清楚的一個老業務系統的日誌框架依賴情況。
image.png

試想一下,假如一開始我們就能遇見到這種緊耦合帶來的問題。在應用和日誌框架之間加入一層抽象解耦。後續的那麼多橋接,那麼多的向後兼容都是可以省掉的麻煩。而我們所要做的事情,實際上也很簡單——就是加一個接口做解耦而已(如下圖所示)
image.png

要給外界提供API的時候

上文已經介紹過JCP和JSR了,大家有空可以去閱讀一些JSR的文檔。不管是做的比較成功的JSR-221(JDBC規範)、JSR-315(Servlet規範),還是比較失敗的JSR-94(規則引擎規範)等等。其本質上都是在定義標準、和制定API。其規範的內容都是抽象的,其對外發布的形式都是接口,它不提供實現,最多會指導實現

還有就是我們通常使用的各種開放平臺的SDK,或者分佈式服務中RPC的二方庫,其包含的主要成分也是接口,其實現不在本地,而是在遠程服務提供方。

類似於這種API的情況,都是在倒逼開發者要把接口想清楚。我想,這也算微服務架構一個漂亮的“副作用”吧。當原來單體應用裏的各種耦合的業務模塊,一旦被服務化之後,就自然而然的變成“面向接口”的了。

通過依賴倒置來實現面向接口(How)

關於依賴倒置,我以前寫過不少文章,來闡述它的重要性。實際上,我上面給出的關於擴展需求的Switch案例,關於解耦的logger案例。其背後用來解決問題的方法論都是依賴倒置

image.png
如上圖所示,依賴倒置原則主要規定了兩件事情:

  1. 高層模塊不應該依賴底層模塊,兩者都應該依賴抽象(如上面的圖2所示)
  2. 抽象不應該依賴細節,細節應該依賴抽象。

我們回頭看一下,不管是Switch的設計,還是抽象Logger的設計,是不是都在遵循上面的兩條定義內容呢。

實際上,DIP(依賴倒置原則)不光在對象設計,模塊設計的時候有用。在架構設計的時候也非常有用,比如,我在做COLA 1.0的時候,和大多數應用架構分層設計一樣,默許了Domain層可以依賴Infrastructure層。
image.png

這種看起來“無傷大雅”的設計,實際上還是存在不小的隱患,也違背了我當初想把業務複雜度和技術複雜度分開的初心,當業務變得更加複雜的時候,這種“偷懶”行爲很可能會導致Domain層墮落成大泥球(Big mud ball)。因此,在COLA 2.0的時候,我決定用DIP來反轉Domain層和Infrastructure層的關係,最終形成如下的結構:
image.png

這樣做的好處是Domain層會變得更加純粹,其好處體現在以下三點:

1、解耦: Domain層完全擺脫了對技術細節(以及技術細節帶來的複雜度)的依賴,只需要安心處理業務邏輯就好了。

2、並行開發: 只要在Domain和Infrastructure約定好接口,可以有兩個同學並行編寫Domain和Infrastructure的代碼。

3、可測試性: 沒有任何依賴的Domain裏面都是POJO的類,單元測試將會變得非常方便,也非常適合TDD的開發。

什麼時候不需要接口

"勁酒雖好,可不要貪杯哦!"

和許多其它軟件原則一樣,面向接口很好,但也不應該是不分背景、不分場合胡亂使用的殺手鐗和尚方寶劍。因爲過多的使用接口,過多的引入間接層也會帶來一些不必要的複雜度。

比如,我就看過有些應用的內部模塊設計的過於“靈活”,給什麼DAO、Convertor都加上一層Interface,但實際情況是,應用中對DAO、Convertor的實現進行替換的可能性極低。類似於這樣的,裝模作樣,裝腔作勢的Interface就屬於可有可無的雞骨頭(比雞肋還低一個檔次)。

就像《Effective Java》的作者Joshua Bloch所說:

“同大多數學科一樣,學習編程的藝術首先要學會基本的規則,然後才能知道什麼時候可以打破這些規則。”

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章