設計模式原則之迪米特法則
迪米特法則的簡寫為 LoD,看清楚中間的那個 o 是小寫。迪米特法則也叫做做最少知識原則(Least Knowledge Principle,簡稱 LKP)說的都是一會事,一個物件應該對其他物件有最少的瞭解,通俗的講一 個類對自己需要耦合或者呼叫的類應該知道的最少,你類內部是怎麼複雜、怎麼的糾纏不清都和我沒關係, 那是你的類內部的事情,我就知道你提供的這麼多 public 方法,我就呼叫這個;迪米特法則包含以下四層 意思:
只和朋友交流。迪米特還有一個英文解釋叫做“Only talk to your immedate friends”,只和直接 的朋友通訊,什麼叫做直接的朋友呢?每個物件都必然會和其他物件有耦合關係,兩個物件之間的耦合就 成為朋友關係,這種關係有很多比如組合、聚合、依賴等等。我們來說個例子說明怎麼做到只和朋友交流。 說是有這麼一個故事,老師想讓體育委員確認一下全班女生來齊沒有,就對他說:“你去把全班女生清 一下。”體育委員沒聽清楚,或者是當時腦子正在回憶什麼東西,就問道:“親哪個?”老師¥#……¥%。 我們來看這個笑話怎麼用程式來實現,先看類圖:
Teacher.java 的源程式如下:
package com.cbf4life.common; import java.util.ArrayList; import java.util.List; /** * I'm glad to share my knowledge with you all. * 老師類 */ public class Teacher { //老師對學生髮布命令, 清一下女生 public void commond(GroupLeader groupLeader){ List<Girl> listGirls = new ArrayList() ; //初始化女生 for(int i=0;i<20;i++){ listGirls.add(new Girl()); } //告訴體育委員開始執行清查任務 groupLeader.countGirls(listGirls); } }
老師就有一個方法,釋出命令給體育委員,去清查一下女生的數量。 下面是體育委員 GroupLeader.java 的源程式:
package com.cbf4life.common; import java.util.List; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 體育委員,這個太難翻譯了都是中國的特色詞彙 */ public class GroupLeader { //有清查女生的工作 public void countGirls(List<Girl> listGirls){ System. out.println(" 女生數量是: "+listGirls.size()); } }
下面是 Girl.java,就宣告一個類,沒有任何的程式碼:
package com.cbf4life.common; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 女生 */ public class Girl { }
我們來看這個業務呼叫類 Client:
package com.cbf4life.common; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 我們使用Client來描繪一下這個場景 */ public class Client { public static void main(String[] args) { Teacher teacher= new Teacher(); //老師釋出命令 teacher.commond(new GroupLeader()); } }
執行的結果如下:
女生數量是: 20
我們回過頭來看這個程式有什麼問題,首先來看 Teacher 有幾個朋友,就一個 GroupLeader 類,這個 就是朋友類,朋友類是怎麼定義的呢? 出現在成員變數、方法的輸入輸出引數中的類被稱為成員朋友類, 迪米特法則說是一個類只和朋友類交流, 但是 commond 方法中我們與 Girl 類有了交流,宣告瞭一個 List動態陣列,也就是與一個陌生的類 Girl 有了交流,這個不好,那我們再來修改一下,類圖還是不變,先修改一下 GroupLeader 類,看原始碼:
package com.cbf4life.common2; import java.util.ArrayList; import java.util.List; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 體育委員,這個太難翻譯了都是中國的特色詞彙 */ public class GroupLeader { //有清查女生的工作 public void countGirls(){ List<Girl> listGirls = new ArrayList<Girl>(); //初始化女生 for(int i=0;i<20;i++){ listGirls.add(new Girl()); } System. out.println(" 女生數量是: "+listGirls.size()); } }
下面是 Teacher.java 程式:
package com.cbf4life.common2; /** * * I'm glad to share my knowledge with you all. * 老師類 */ public class Teacher { //老師對學生髮布命令, 清一下女生 public void commond(GroupLeader groupLeader){ //告訴體育委員開始執行清查任務 groupLeader.countGirls(); } }
程式做了一個簡單的修改,就是把 Teacher 中的對 List初始化(這個是有業務意義的,產生出 全班的所有人員)移動到了 GroupLeader 的 countGrils 方法中,避開了 Teacher 類對陌生類 Girl 的訪問, 減少系統間的耦合。記住了,一個類只和朋友交流,不與陌生類交流, 不要出現 getA().getB().getC().getD() 這種情況(在一種極端的情況下是允許出現這種訪問:每一個點號後面的返回型別都相同),那當然還要和 JDK API 提供的類交流,否則你想脫離編譯器存在呀!
朋友間也是有距離的。人和人之間是有距離的,太遠就不是朋友了,太近就渾身不自在,這和類間關 系也是一樣,即使朋友類也不能無話不說,無所不知。大家在專案中應該都碰到過這樣的需求:呼叫一個 類,然後必須是先執行第一個方法,然後是第二個方法,根據返回結果再來看是否可以呼叫第三個方法, 或者第四個方法等等,我們用類圖表示一下:
很簡單的類圖,實現軟體安裝過程的第一步做什麼、第二步做什麼、第三步做什麼這樣一個過程,我 們來看三個類的原始碼,先看 Wizard 的原始碼:
package com.cbf4life.common3; import java.util.Random; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 按照步驟執行的業務邏輯類 */ public class Wizard { private Random rand = new Random(System. currentTimeMillis()); public int first(){ System. out.println(" 執行第一個方法..."); return rand.nextInt(100); } //第二步 public int second(){ System. out.println(" 執行第二個方法..."); return rand.nextInt(100); } //第三個方法 public int third(){ System. out.println(" 執行第三個方法..."); return rand.nextInt(100); } }
分別定義了三個步驟方法,每個步驟中都有相關的業務邏輯完成指定的任務,我們使用一個隨機函式 來代替業務執行的返回值。再來看軟體安裝過程 InstallSoftware 原始碼:
package com.cbf4life.common3; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 業務組裝類,負責呼叫各個步驟 */ public class InstallSoftware { public void installWizard(Wizard wizard){ int first = wizard.first(); //根據first返回的結果,看是否需要執行second if(first>50){ int second = wizard.second(); if(second>50){ int third = wizard.third(); if(third >50){ wizard.first(); } } } } }
其中 installWizard 就是一個嚮導式的安裝步驟,我們看場景是怎麼呼叫的:
package com.cbf4life.common3; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 業務場景 */ public class Client { public static void main(String[] args) { InstallSoftware invoker = new InstallSoftware(); invoker.installWizard(new Wizard()); } }
這個程式很簡單,執行結果和隨機數有關,我就不貼上上來了。我們想想這個程式有什麼問題嗎? Wizard 類把太多的方法暴露給 InstallSoftware 類了,這樣耦合關係就非常緊了,我想修改一個方法的返 回值,本來是 int 的,現在修改為 boolean,你看就需要修改其他的類,這樣的耦合是極度不合適的, 迪米 特法則就要求類“小氣”一點,儘量不要對外公佈太多的 public 方法和非靜態的 public 變數, 儘量內斂, 多使用 private,package-private、protected 等訪問許可權。我們來修改一下類圖:
我們再來看一下程式的變更,先看 Wizard 程式:
package com.cbf4life.common4; import java.util.Random; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 按照步驟執行的業務邏輯類 */ public class Wizard { private Random rand = new Random(System. currentTimeMillis()); //第一步 private int first(){ System. out.println(" 執行第一個方法..."); return rand.nextInt(100); } //第二步 private int second(){ System. out.println(" 執行第二個方法..."); return rand.nextInt(100); } //第三個方法 private int third(){ System. out.println(" 執行第三個方法..."); return rand.nextInt(100); } //軟體安裝過程 public void installWizard(){ int first = this.first(); //根據first返回的結果,看是否需要執行second if(first>50){ int second = this.second(); if(second>50){ int third = this.third(); if(third >50){ this.first(); } } } } }
三個步驟的訪問許可權修改為 private,同時把 installeWizad 移動的 Wizard 方法中,這樣 Wizard 類就 對外只公佈了一個 public 方法,類的高內聚特定顯示出來了。我們再來看 InstallSoftware 原始碼:
package com.cbf4life.common4; /** * @author cbf4Life cbf4life@126.com * I'm glad to share my knowledge with you all. * 業務組裝類,負責呼叫各個步驟 */ public class InstallSoftware { public void installWizard(Wizard wizard){ //不廢話,直接呼叫 wizard.installWizard(); } }
Client 類沒有任何改變,就不在拷貝了,這樣我們的程式就做到了弱耦合,一個類公佈越多的 public 屬性或方法,修改的涉及面也就越大,也就是變更引起的風險就越大。因此為了保持朋友類間的距離,你 需要做的是:減少 public 方法,多使用 private、package-private(這個就是包型別,在類、方法、變數 前不加訪問許可權,則預設為包型別)protected 等訪問許可權,減少非 static 的 public 屬性,如果成員變數 或方法能加上 final 關鍵字就加上,不要讓外部去改變它。
是自己的就是自己的。在專案中有一些方法,放在本類中也可以,放在其他類中也沒有錯誤,那怎麼 去衡量呢?你可以堅持這樣一個原則: 如果一個方法放在本類中,即不增加類間關係,也對本類不產生負 面影響,就放置在本類中。
謹慎使用 Serializable。實話說,這個問題會很少出現的,即使出現也會馬上發現問題。是怎麼回事呢? 舉個例子來說,如果你使用 R M I 的方式傳遞一個物件 VO( Va lu e Object),這個物件就必須使用 Serializable 介面,也就是把你的這個物件進行序列化,然後進行網路傳輸。突然有一天,客戶端的 VO 物件修改了一個 屬性的訪問許可權,從 private 變更為 public 了,如果伺服器上沒有做出響應的變更的話,就會報序列化失敗。 這個應該屬於專案管理範疇,一個類或介面客戶端變更了,而服務端沒有變更,那像話嗎?!
迪米特法則的核心觀念就是類間解耦,弱耦合,只有弱耦合了以後,類的複用率才可以提高,其要求 的結果就是產生了大量的中轉或跳轉類,類只能和朋友交流,朋友少了你業務跑不起來,朋友多了,你項 目管理就複雜,大家在使用的時候做相互權衡吧。
不知道大家有沒有聽過這樣一個理論:“任何 2 個素不相識的人中間最多隻隔著 6 個人,即只用 6 個人 就可以將他們聯絡在一起”,這個理論的學名叫做“六度分離”,應用到我們專案中就是說我和我要呼叫的 類之間最多有 6 次傳遞,呵呵,這隻能讓大家當個樂子來看,在實際專案中你跳兩次才訪問到一個類估計 你就會想辦法了,這也是合理的,迪米特法則要求我們類間解耦,但是解耦是有限度的,除非是計算機的 最小符號二進位制的 0 和 1,那才是完全解耦,我們在實際的專案中時,需要適度的考慮這個法則,別為了套 用法則而做專案,法則只是一個參考,你跳出了這個法則,也不會有人判你刑,專案也未必會失敗,這就 需要大家使用的是考慮如何度量法則了。
相關文章
- 設計原則之【迪米特法則】
- 物件導向設計原則之迪米特法則物件
- 軟體設計原則—迪米特法則
- 設計模式六大原則(五)----迪米特法則設計模式
- 設計模式六大原則(5):迪米特法則設計模式
- 嘻哈說:設計模式之迪米特法則設計模式
- 設計模式的七大原則(6) --迪米特法則設計模式
- 迪米特法則——合理的封裝封裝
- 2分鐘通俗理解迪米特法則,架構設計築基必看架構
- Javascript 設計模式之設計原則JavaScript設計模式
- 小話設計模式原則之(4):開閉原則OCP設計模式
- 設計原則 設計模式設計模式
- 設計模式 - 設計原則設計模式
- 【設計模式】設計原則設計模式
- 設計模式之7大原則設計模式
- 小話設計模式原則之(3):介面隔離原則ISP設計模式
- 設計原則之【介面隔離原則】
- 【設計模式——六原則】設計模式
- 設計模式的設計原則設計模式
- 設計模式之六大原則設計模式
- Java設計模式之依賴倒置原則Java設計模式
- 設計原則之【依賴反轉原則】
- 設計原則之【單一職責原則】
- 設計原則之【開放封閉原則】
- 設計原則之【裡式替換原則】
- 小話設計模式原則之(2):單一職責原則SRP設計模式
- JavaScript設計模式(一)設計原則JavaScript設計模式
- 設計模式(07)——設計原則(2)設計模式
- 設計模式(06)——設計原則(1)設計模式
- 設計模式學習-設計原則設計模式
- 我學設計模式 之 物件導向設計原則設計模式物件
- 物件導向設計原則之開閉原則物件
- 設計模式六大原則(六)----開閉原則設計模式
- 設計模式六大原則(6):開閉原則設計模式
- 物件導向設計原則之合成複用原則物件
- 物件導向設計原則之介面隔離原則物件
- 物件導向設計原則之里氏代換原則物件
- 設計模式六大設計原則設計模式