轉載自:http://www.jianshu.com/p/14589fb6978e (作者簡書:涅槃1992)
揭秘迪米特法則
迪米特法則(Law of demeter,縮寫是LOD)要求:一個對象應該對其他對象保持最少了解, 通縮的講就是一個類對自己依賴的類知道的越少越好,也就是對于被依賴的類,向外公開的方法應該盡可能的少。
迪米特法則還有一種解釋:Only talk to your immediate friends,即只與直接朋友通信.首先來解釋編程中的朋友:兩個對象之間的耦合關系稱之為朋友,通常有依賴,關聯,聚合和組成等.而直接朋友則通常表現為關聯,聚合和組成關系,即兩個對象之間聯系更為緊密,通常以成員變量,方法的參數和返回值的形式出現.
那么為什么說是要與直接朋友通信呢?觀察直接朋友出現的地方,我們發現在直接朋友出現的地方,大部分情況下可以接口或者父類來代替,可以增加靈活性. (需要注意,在考慮這個問題的時候,我們只考慮新增的類,而忽視java為我們提供的基礎類.)
實例演示
不難發現,迪米特法則強調了一下兩點:
- 第一要義:從被依賴者的角度來說:只暴露應該暴露的方法或者屬性,即在編寫相關的類的時候確定方法/屬性的權限
- 第二要義:從依賴者的角度來說,只依賴應該依賴的對象
先來解釋第一點,我們使用計算機來說明,以關閉計算機為例:
當我們按下計算機的關機按鈕的時候,計算機會執行一些列的動作會被執行:比如保存當前未完成的任務,然后是關閉相關的服務,接著是關閉顯示器,最后是關閉電源,這一系列的操作以此完成后,計算機才會正式被關閉。
現在,我們來用簡單的代碼表示這個過程,在不考慮迪米特法則情況下,我們可能寫出以下代碼
//計算機類
public class Computer{public void saveCurrentTask(){//do something}public void closeService(){//do something}public void closeScreen(){//do something}public void closePower(){//do something}public void close(){saveCurrentTask();closeService();closeScreen();closePower();}
}//人
public class Person{private Computer c;...public void clickCloseButton(){//現在你要開始關閉計算機了,正常來說你只需要調用close()方法即可,//但是你發現Computer所有的方法都是公開的,該怎么關閉呢?于是你寫下了以下關閉的流程: c.saveCurrentTask();c.closePower();c.close();//亦或是以下的操作 c.closePower();//還可能是以下的操作c.close();c.closePower();}}
發現上面的代碼中的問題了沒?
我們觀察clickCloseButton()方法,我們發現這個方法無法編寫:c是一個完全暴露的對象,其方法是完全公開的,那么對于Person來說,當他想要執行關閉的時候,卻發現不知道該怎么操作:該調用什么方法?靠運氣猜么?如果Person的對象是個不按常理出牌的,那這個Computer的對象豈不是要被搞壞么?
迪米特法則第一要義
現在我們來看看迪米特法則的第一點:從被依賴者的角度,只應該暴露應該暴露的方法。那么這里的c對象應該哪些方法應該是被暴露的呢?很顯然,對于Person來說,只需要關注計算機的關閉操作,而不關心計算機會如何處理這個關閉操作,因此只需要暴露close()
方法即可。
那么上述的代碼應該被修改為:
//計算機類
public class Computer{private void saveCurrentTask(){//do something}private void closeService(){//do something}private void closeScreen(){//do something}private void closePower(){//do something}public void close(){saveCurrentTask();closeService();closeScreen();closePower();}
}//人
public class Person{private Computer c;...public void clickCloseButton(){c.close();}}
看一下它的類圖:
接下來,我們繼續來看迪米特法則的第二層含義:從依賴者的角度來說,只依賴應該依賴的對象。 這句話令人有點困惑,什么叫做應該依賴的對象呢?我們還是用上面“關閉計算機”的例子來說明: 準確的說,計算機包括操作系統和相關硬件,我們可以將其劃分為System對象和Container對象。當我們關閉計算機的時候,本質上是向操作系統發出了關機指令,而實則我們只是按了一下關機按鈕,也就是我們并沒有依賴System的對象,而是依賴了Container。這里Container就是我們上面所說的直接朋友---只和直接朋友通信.
我們就這點,繼續深入討論一下: only talk to your immedate friends
這句話只說明了要和直接朋友通信,但是我覺得這還不完整,我更愿意將其補充為: make sure your friends,only talk to your immedate friends,don't speak to strangers. 大意是:確定你真正的朋友,并只和他們通信,并且不要和陌生人講話.這樣做有個很大的好處就是,能夠簡化對象與對象之間的通信,進而減輕依賴,提供更高的靈活性,當然也可以提供一定的安全性.
現在來想想現實世界中的這么一種情況:你是一個令人矚目的公眾人物,周圍的每個人都知道你的名字,當你獨自走在大街上的時候會是怎么樣的一種場景?每個人都想要和你聊天!,和你交換信息!!接著,你發現自己已經寸步難行了.如果這時候你有一個經紀人,來幫你應對周圍的人,而你就只和這個經紀人通信,這樣就大大減輕了你的壓力,不是么?此時,這個經濟人就相當于你的直接朋友.
迪米特法則第二要義
現在,我們再回顧"關機計算機"這個操作,前面的代碼只是遵從了"暴漏應該暴漏的方法"這一點,現在我們結合第二點來進行改進:System和Container相比,System并非Person的直接朋友,而Container才是(Person直接打交道的是Container).因此我們需要將原有的Computer拆分成System和Cotainer,然后使Person只與Container通信,因此代碼修改為:
//操作系統
public class System{private void saveCurrentTask(){//do something}private void closeService(){//do something}private void closeScreen(){//do something}private void closePower(){//do something}public void close(){saveCurrentTask();closeService();closeScreen();closePower();}
}//硬件設備容器
public class Container{private System mSystem;public void sendCloseCommand(){mSystem.close();}
}//人
ublic class Person{private Container c;....public void clickCloseButton(){c.sendCloseCommand();}}
來看一下它的類圖:
對比這兩種方案,明顯這種方案二的解耦程度更高,靈活大大增強.不難發現,這應用了我們前面提到的依賴倒置,即面向接口編程.
除此之外,我們發現隨著不斷的改進,類的數量也在不斷的增加,從2個增加到5個,這意味著為了解耦和提高靈活性通常要編寫的類的數量會翻倍.因此,你需要在這做一個權衡,切莫刻意為了追求設計,而導致整個系統非常的冗余,最終可能得不償失.
總結
有人會覺得Container像是一個中介(代理).沒錯,我們確實可以稱其為中介,但這并不能否認他是我們的直接朋友:在很多情況下,中介可以說是我們的一種代表,因此將其定義為直接朋友是沒有任何問題的.比如,當你想要租房的時候,你可以找房屋中介,對方會按照你的標準為你尋找合適的住房.但是問題來了:那么做一件事情需要多少中介呢?總不能是我委托一個中介A幫我找房子,但中介A又委托了中介B,中介B又委托了中介C....等等,如果真的是這樣,那還不如我自己去找房子效率更高.在實際開發中,委托的層次要控制在6層以下,多余6層以上的會使得系統過分的冗余和并切會委托層次過多而導致開發人員無法正確的理解流程,產生風險的可能會大大提高.
到目前,我們已經徹底的了解了迪米特法則.