白話設(shè)計(jì)——淺談迪米特法則

前面我們談到了幾種類與類之間的關(guān)系,現(xiàn)在我們來深入一下對(duì)象與對(duì)象之間的通信問題.
為什么要深入對(duì)象與對(duì)象之間的通信呢,其根本在于,系統(tǒng)中不會(huì)存在唯一的對(duì)象,不同的對(duì)象勢必要相互進(jìn)行交流.


初學(xué)者的問題

在我們剛開始學(xué)習(xí)編程的時(shí)候竟痰,通常會(huì)將所有的方法都聲明為公有(public),但隨著我們代碼量的增加,我們都會(huì)遇到一個(gè)典型的問題:

在調(diào)用某個(gè)對(duì)象的方法時(shí)模庐,我們發(fā)現(xiàn)編譯器提示這個(gè)對(duì)象所有的方法舍败,這意味著該對(duì)象處在不安全的狀態(tài)威沫。為什么這么說呢尊搬?如果我們將這個(gè)對(duì)象比作一個(gè)人绩卤,那么這個(gè)人在別人面前是赤裸的途样,沒有任何隱私,這讓別人有機(jī)會(huì)觀察你的一切行為濒憋,并某刻致命一擊何暇。除此之外,這個(gè)完全暴露的人凛驮,也會(huì)讓別人不知所措裆站。

這顯然不是我們想要的,因此我們需要某種機(jī)制來限制的對(duì)象信息的公開:哪些信息是可以公開的黔夭,哪些是不可以公開的宏胯,在java中,我們通過方法的權(quán)限來實(shí)現(xiàn)這一點(diǎn)本姥,比如private修飾的方法只有對(duì)象自己內(nèi)部可以調(diào)用肩袍,public修飾的方法是公開給其他對(duì)象的等。

現(xiàn)在婚惫,你可能已經(jīng)明白氛赐,java的設(shè)計(jì)者為什么要“多此一舉”的為方法設(shè)計(jì)權(quán)限了魂爪。那么有人會(huì)問,我該怎么確定哪個(gè)方法應(yīng)該被設(shè)計(jì)成公有的艰管,哪些又應(yīng)該被設(shè)計(jì)成私有的呢滓侍?

當(dāng)你心里有這個(gè)疑問的時(shí)候,說明你已經(jīng)開始關(guān)注我們經(jīng)常提到的面向?qū)ο缶幊痰脑瓌t之一:封裝牲芋,即如何劃分對(duì)象的結(jié)構(gòu)撩笆。
我們都知道對(duì)象的結(jié)構(gòu)的可被劃分為靜態(tài)屬性和動(dòng)態(tài)屬性,所謂的靜態(tài)屬性就是值對(duì)象固有的屬性缸浦,比如任何一個(gè)生命體都有年齡夕冲,而動(dòng)態(tài)屬性也稱為行為屬性,指的是對(duì)象所表現(xiàn)出來的行為餐济,比如袋鼠能跳耘擂,能呼吸等。而這靜態(tài)屬性和動(dòng)態(tài)屬性又可以細(xì)分為可公開的靜態(tài)屬性絮姆,可公開的動(dòng)態(tài)屬性等。也就是說秩霍,劃分對(duì)象的結(jié)構(gòu)實(shí)則就是確定某個(gè)對(duì)象的動(dòng)態(tài)屬性和靜態(tài)屬性篙悯,在此基礎(chǔ)上再來確定屬性是否可公開等。

不難發(fā)現(xiàn)铃绒,這個(gè)過程和我們的認(rèn)知的思維過程很類似:大腦試圖從各種各樣的的物體中抽取特征鸽照。比如,我們看到貓颠悬,狗矮燎,仙人掌,為了能區(qū)分它們赔癌,我們的大腦會(huì)對(duì)這三者進(jìn)行特征抽取诞外,比如貓和狗都可以移動(dòng),有眼睛灾票,會(huì)叫峡谊,有爪子,而仙人掌則是不可移動(dòng)刊苍,有刺既们,不能叫等,通過這種特種抽取正什,我們能區(qū)分出動(dòng)物和植物的區(qū)別啥纸。換言之,我們之所以能區(qū)分出不同的物體婴氮,都是因?yàn)槲覀兊拇竽X已經(jīng)默默的為我們做了特征抽取的工作斯棒,這個(gè)過程如果由我們主動(dòng)去做就稱之為抽象編程馒索。


揭秘迪米特法則

迪米特法則(Law of demeter,縮寫是LOD)要求:一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象保持最少了解, 通縮的講就是一個(gè)類對(duì)自己依賴的類知道的越少越好名船,也就是對(duì)于被依賴的類绰上,向外公開的方法應(yīng)該盡可能的少。

迪米特法則還有一種解釋:Only talk to your immediate friends渠驼,即只與直接朋友通信.首先來解釋編程中的朋友:兩個(gè)對(duì)象之間的耦合關(guān)系稱之為朋友,通常有依賴,關(guān)聯(lián),聚合和組成等.而直接朋友則通常表現(xiàn)為關(guān)聯(lián),聚合和組成關(guān)系,即兩個(gè)對(duì)象之間聯(lián)系更為緊密,通常以成員變量,方法的參數(shù)和返回值的形式出現(xiàn).

那么為什么說是要與直接朋友通信呢?觀察直接朋友出現(xiàn)的地方,我們發(fā)現(xiàn)在直接朋友出現(xiàn)的地方,大部分情況下可以接口或者父類來代替,可以增加靈活性.
(需要注意,在考慮這個(gè)問題的時(shí)候,我們只考慮新增的類,而忽視java為我們提供的基礎(chǔ)類.)

實(shí)例演示

不難發(fā)現(xiàn),迪米特法則強(qiáng)調(diào)了一下兩點(diǎn):

  • 第一要義:從被依賴者的角度來說:只暴露應(yīng)該暴露的方法或者屬性蜈块,即在編寫相關(guān)的類的時(shí)候確定方法/屬性的權(quán)限
  • 第二要義:從依賴者的角度來說,只依賴應(yīng)該依賴的對(duì)象

先來解釋第一點(diǎn)迷扇,我們使用計(jì)算機(jī)來說明,以關(guān)閉計(jì)算機(jī)為例:

當(dāng)我們按下計(jì)算機(jī)的關(guān)機(jī)按鈕的時(shí)候百揭,計(jì)算機(jī)會(huì)執(zhí)行一些列的動(dòng)作會(huì)被執(zhí)行:比如保存當(dāng)前未完成的任務(wù),然后是關(guān)閉相關(guān)的服務(wù)蜓席,接著是關(guān)閉顯示器器一,最后是關(guān)閉電源,這一系列的操作以此完成后厨内,計(jì)算機(jī)才會(huì)正式被關(guān)閉祈秕。

現(xiàn)在,我們來用簡單的代碼表示這個(gè)過程,在不考慮迪米特法則情況下雏胃,我們可能寫出以下代碼

//計(jì)算機(jī)類
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(){
      //現(xiàn)在你要開始關(guān)閉計(jì)算機(jī)了请毛,正常來說你只需要調(diào)用close()方法即可,
      //但是你發(fā)現(xiàn)Computer所有的方法都是公開的瞭亮,該怎么關(guān)閉呢方仿?于是你寫下了以下關(guān)閉的流程:        
        c.saveCurrentTask();
        c.closePower();
        c.close();
        
        //亦或是以下的操作        
        c.closePower();
        
        //還可能是以下的操作
        c.close();
        c.closePower();
    }

}

發(fā)現(xiàn)上面的代碼中的問題了沒?
我們觀察clickCloseButton()方法,我們發(fā)現(xiàn)這個(gè)方法無法編寫:c是一個(gè)完全暴露的對(duì)象,其方法是完全公開的统翩,那么對(duì)于Person來說仙蚜,當(dāng)他想要執(zhí)行關(guān)閉的時(shí)候,卻發(fā)現(xiàn)不知道該怎么操作:該調(diào)用什么方法?靠運(yùn)氣猜么?如果Person的對(duì)象是個(gè)不按常理出牌的,那這個(gè)Computer的對(duì)象豈不是要被搞壞么?

迪米特法則第一要義

現(xiàn)在我們來看看迪米特法則的第一點(diǎn):從被依賴者的角度厂汗,只應(yīng)該暴露應(yīng)該暴露的方法委粉。那么這里的c對(duì)象應(yīng)該哪些方法應(yīng)該是被暴露的呢?很顯然面徽,對(duì)于Person來說艳丛,只需要關(guān)注計(jì)算機(jī)的關(guān)閉操作,而不關(guān)心計(jì)算機(jī)會(huì)如何處理這個(gè)關(guān)閉操作趟紊,因此只需要暴露close()方法即可氮双。
那么上述的代碼應(yīng)該被修改為:

//計(jì)算機(jī)類
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();
    }

}

看一下它的類圖:


這里寫圖片描述

接下來,我們繼續(xù)來看迪米特法則的第二層含義:從依賴者的角度來說霎匈,只依賴應(yīng)該依賴的對(duì)象戴差。
這句話令人有點(diǎn)困惑,什么叫做應(yīng)該依賴的對(duì)象呢铛嘱?我們還是用上面“關(guān)閉計(jì)算機(jī)”的例子來說明:
準(zhǔn)確的說暖释,計(jì)算機(jī)包括操作系統(tǒng)和相關(guān)硬件袭厂,我們可以將其劃分為System對(duì)象和Container對(duì)象。當(dāng)我們關(guān)閉計(jì)算機(jī)的時(shí)候球匕,本質(zhì)上是向操作系統(tǒng)發(fā)出了關(guān)機(jī)指令纹磺,而實(shí)則我們只是按了一下關(guān)機(jī)按鈕,也就是我們并沒有依賴System的對(duì)象亮曹,而是依賴了Container橄杨。這里Container就是我們上面所說的直接朋友---只和直接朋友通信.

我們就這點(diǎn),繼續(xù)深入討論一下:
only talk to your immedate friends
這句話只說明了要和直接朋友通信,但是我覺得這還不完整,我更愿意將其補(bǔ)充為:
make sure your friends,only talk to your immedate friends,don't speak to strangers.
大意是:確定你真正的朋友,并只和他們通信,并且不要和陌生人講話.這樣做有個(gè)很大的好處就是,能夠簡化對(duì)象與對(duì)象之間的通信,進(jìn)而減輕依賴,提供更高的靈活性,當(dāng)然也可以提供一定的安全性.

現(xiàn)在來想想現(xiàn)實(shí)世界中的這么一種情況:你是一個(gè)令人矚目的公眾人物,周圍的每個(gè)人都知道你的名字,當(dāng)你獨(dú)自走在大街上的時(shí)候會(huì)是怎么樣的一種場景?每個(gè)人都想要和你聊天!,和你交換信息!!接著,你發(fā)現(xiàn)自己已經(jīng)寸步難行了.如果這時(shí)候你有一個(gè)經(jīng)紀(jì)人,來幫你應(yīng)對(duì)周圍的人,而你就只和這個(gè)經(jīng)紀(jì)人通信,這樣就大大減輕了你的壓力,不是么?此時(shí),這個(gè)經(jīng)濟(jì)人就相當(dāng)于你的直接朋友.


迪米特法則第二要義

現(xiàn)在,我們?cè)倩仡?關(guān)機(jī)計(jì)算機(jī)"這個(gè)操作,前面的代碼只是遵從了"暴漏應(yīng)該暴漏的方法"這一點(diǎn),現(xiàn)在我們結(jié)合第二點(diǎn)來進(jìn)行改進(jìn):System和Container相比,System并非Person的直接朋友,而Container才是(Person直接打交道的是Container).因此我們需要將原有的Computer拆分成System和Cotainer,然后使Person只與Container通信,因此代碼修改為:

//操作系統(tǒng)
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();
    }
}

//硬件設(shè)備容器
public class Container{
    private System mSystem;
    
    public void sendCloseCommand(){
        mSystem.close();
    }
}

//人
ublic class Person{
    private Container c;
    ....
    
    public void clickCloseButton(){
       c.sendCloseCommand();
    }

}

來看一下它的類圖:


這里寫圖片描述

重構(gòu),改善既有設(shè)計(jì)

在上文中,我們還提到,直接朋友出現(xiàn)的地方,我們可以采用其接口或者父類來代替.那么在這里,我們就可以為Container和System提供相應(yīng)的接口

//System interface
public interface ISystem{
    void close();
}

//System
public class System implements ISystem{
    
    private void saveCurrentTask(){
        //do something
    }
    
    private void closeService(){
        //do something
    }
    
    private void closeScreen(){
        //do something
    }
    
    private void closePower(){
        //do something
    }
    
    @override
    public void close(){
        saveCurrentTask();
        closeService();
        closeScreen();
        closePower();
    }
}

//IContainer interface
public interface IContainer{
    void sendCloseCommand();
}

//Contanier
public class Container implements IContainer{
    private System mSystem;
    
    @override
    public void sendCloseCommand(){
        mSystem.close();
    }
}

//Person
ublic class Person{
    private IContainer c;
    ....
    
    public void clickCloseButton(){
       c.sendCloseCommand();
    }

}

來看一下它的類圖:


這里寫圖片描述

對(duì)比這兩種方案,明顯這種方案二的解耦程度更高,靈活大大增強(qiáng).不難發(fā)現(xiàn),這應(yīng)用了我們前面提到的依賴倒置,即面向接口編程.

除此之外,我們發(fā)現(xiàn)隨著不斷的改進(jìn),類的數(shù)量也在不斷的增加,從2個(gè)增加到5個(gè),這意味著為了解耦和提高靈活性通常要編寫的類的數(shù)量會(huì)翻倍.因此,你需要在這做一個(gè)權(quán)衡,切莫刻意為了追求設(shè)計(jì),而導(dǎo)致整個(gè)系統(tǒng)非常的冗余,最終可能得不償失.


總結(jié)

有人會(huì)覺得Container像是一個(gè)中介(代理).沒錯(cuò),我們確實(shí)可以稱其為中介,但這并不能否認(rèn)他是我們的直接朋友:在很多情況下,中介可以說是我們的一種代表,因此將其定義為直接朋友是沒有任何問題的.比如,當(dāng)你想要租房的時(shí)候,你可以找房屋中介,對(duì)方會(huì)按照你的標(biāo)準(zhǔn)為你尋找合適的住房.但是問題來了:那么做一件事情需要多少中介呢?總不能是我委托一個(gè)中介A幫我找房子,但中介A又委托了中介B,中介B又委托了中介C....等等,如果真的是這樣,那還不如我自己去找房子效率更高.在實(shí)際開發(fā)中,委托的層次要控制在6層以下,多余6層以上的會(huì)使得系統(tǒng)過分的冗余和并切會(huì)委托層次過多而導(dǎo)致開發(fā)人員無法正確的理解流程,產(chǎn)生風(fēng)險(xiǎn)的可能會(huì)大大提高.

到目前,我們已經(jīng)徹底的了解了迪米特法則.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市照卦,隨后出現(xiàn)的幾起案子式矫,更是在濱河造成了極大的恐慌,老刑警劉巖役耕,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件采转,死亡現(xiàn)場離奇詭異,居然都是意外死亡瞬痘,警方通過查閱死者的電腦和手機(jī)故慈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來图云,“玉大人惯悠,你說我怎么就攤上這事】⒖觯” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵筒严,是天一觀的道長丹泉。 經(jīng)常有香客問我,道長鸭蛙,這世上最難降的妖魔是什么摹恨? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮娶视,結(jié)果婚禮上晒哄,老公的妹妹穿的比我還像新娘。我一直安慰自己肪获,他們只是感情好寝凌,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著孝赫,像睡著了一般较木。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上青柄,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天伐债,我揣著相機(jī)與錄音预侯,去河邊找鬼。 笑死峰锁,一個(gè)胖子當(dāng)著我的面吹牛萎馅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播虹蒋,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼糜芳,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了千诬?” 一聲冷哼從身側(cè)響起耍目,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎徐绑,沒想到半個(gè)月后邪驮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡傲茄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年毅访,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盘榨。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喻粹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出草巡,到底是詐尸還是另有隱情守呜,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布山憨,位于F島的核電站查乒,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏郁竟。R本人自食惡果不足惜玛迄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望棚亩。 院中可真熱鬧蓖议,春花似錦、人聲如沸讥蟆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽攻询。三九已至从撼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背低零。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國打工婆翔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人掏婶。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓啃奴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親雄妥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子最蕾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 前面我們談到了幾種類與類之間的關(guān)系,現(xiàn)在我們來深入一下對(duì)象與對(duì)象之間的通信問題.為什么要深入對(duì)象與對(duì)象之間的通信呢...
    涅槃1992閱讀 2,653評(píng)論 0 3
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)老厌,斷路器瘟则,智...
    卡卡羅2017閱讀 134,600評(píng)論 18 139
  • 設(shè)計(jì)模式基本原則 開放-封閉原則(OCP),是說軟件實(shí)體(類枝秤、模塊醋拧、函數(shù)等等)應(yīng)該可以拓展,但是不可修改淀弹。開-閉原...
    西山薄涼閱讀 3,753評(píng)論 3 13
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法丹壕,類相關(guān)的語法,內(nèi)部類的語法薇溃,繼承相關(guān)的語法菌赖,異常的語法,線程的語...
    子非魚_t_閱讀 31,587評(píng)論 18 399
  • 簡述:類似KVO沐序,監(jiān)聽對(duì)象 系統(tǒng)的 Notification 例如:系統(tǒng)鍵盤的 UIKeyboardDidChan...
    居然是村長閱讀 385評(píng)論 0 2