設計模式原則之迪米特法則


迪米特法則的簡寫為 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,那才是完全解耦,我們在實際的項目中時,需要適度的考慮這個法則,別為了套 用法則而做項目,法則只是一個參考,你跳出了這個法則,也不會有人判你刑,項目也未必會失敗,這就 需要大家使用的是考慮如何度量法則了。(轉自24種設計模式)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM