iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 9
0

本文同步分享於個人blog

終於到了最後一個Design Principle了。若對其他Design Principle不熟悉的,可以先看前面的文章喔!!

  • 定義


各單元對其他單元所知應當有限:只瞭解與目前單元最相關之單元

白話文年糕:別太好奇,跟你朋友聊天就好。

迪米特法則,又稱最少知識原則。就是一個類別,對自己需要呼叫或耦合的類別不需要知道的太多,只需要知道該類別提供的public方法,不需要知道內部的實作。

  • LoD的四層含義


在使用LoF時,除了知道了定義以外,還要了解LoF中的四層含義

1. 只和朋友交流

LoD有一段英文的解釋,Only talk to your immediate friends; 只與直接的朋友講話。什麼是朋友?兩個物件只要有耦合,就會成為朋友。好比說:合成、聚合及依賴。
例如:老師要班長清點班上人數,就清點這件事來說,老師只需要跟班長有耦合,不需要跟班級有耦合。

class Clazz {
    public void getInfo(){
        System.out.println("共30人!!");
    }
}
class ClassLeader {
    private Clazz c;
    private void getClazz(){
        this.c = new Clazz();
    }
    public void getClassInfo(){
		this.getClazz();
		this.c.getInfo();
    }
}
class Teacher {
    private ClassLeader cl;
    private void getClassLeader(){
        this.cl = new ClassLeader();
    }
    public void getClassInfo(){
        this.getClassLeader();
        this.cl.getClassInfo();
    }
}
public class MyClass {
    public static void main(String args[]) {
      Teacher t = new Teacher();
      t.getClassInfo();
    }
}

output

共30人!!
2. 朋友間也應該有距離

就算是朋友,也不可能什麼都知道。一個公開類別的public屬性或方法越多,修改時的風險也會跟著提高,所以在系統在設計時應該反覆衡量以下幾點:

1. 是否還可以再减少public方法和屬性,
2. 是否可以修改為private、package、protected等權限,
3. 是否可以加上final關鍵字。

可以用安裝java sdk來舉例,一般安裝要先下載JDK,然後設定環境變數,最後確認是否安裝。可以這樣呈現:

import java.util.Random;

class JavaJDK {
    private Random rand = new Random(System.currentTimeMillis());

    public int downloadJDK(){
        System.out.println("下載JAVA JDK");
        return rand.nextInt(10); // 先用隨機數字的狀態表示狀態
    }
    public int setEnv(){
        System.out.println("設定JAVA環境變數");
        return rand.nextInt(10);
    }
    public int checkVer(){
        System.out.println("確認JAVA版本");
        return rand.nextInt(10);
    }
}
class InstallSoftware {
    public void installJava(){
        JavaJDK j = new JavaJDK();
        int jdkStatus = j.downloadJDK();
        if (jdkStatus != 0){
            int envStatus = j.setEnv();
            if (envStatus != 0){
                int verStatus = j.checkVer();
                if (verStatus != 0){
                    System.out.println("安裝完成!!");
                }
            }
        }
    }
}
public class MyComputer {
    public static void main(String args[]) {
        InstallSoftware i = new InstallSoftware();
        i.installJava();
    }
}

output

下載JAVA JDK
設定JAVA環境變數
確認JAVA版本
安裝完成!!

這程式跑起來很正常,卻也存在著一些問題。JavaJDK類別開放太多的public方法給InstallJava了。若今天回傳的int型別要變成boolean,也要修改InstallJava,這就會有一些風險產生。所以我們需要優化一下程式:

import java.util.Random;

class JavaJDK {
    private Random rand = new Random(System.currentTimeMillis());

    private int downloadJDK(){
        System.out.println("下載JAVA JDK");
        return rand.nextInt(10); // 先用隨機數字的狀態表示狀態
    }
    private int setEnv(){
        System.out.println("設定JAVA環境變數");
        return rand.nextInt(10);
    }
    public int checkVer(){
        System.out.println("確認JAVA版本");
        return rand.nextInt(10);
    }
    public void install(){
        int jdkStatus = this.downloadJDK();
        if (jdkStatus != 0){
            int envStatus = this.setEnv();
            if (envStatus != 0){
                int verStatus = this.checkVer();
                if (verStatus != 0){
                    System.out.println("安裝完成!!");
                }
            }
        }
    }
}
class InstallSoftware {
    public void installJava(){
        JavaJDK j = new JavaJDK();
        j.install();
    }
}
public class MyComputer{
    public static void main(String args[]) {
        InstallSoftware i = new InstallSoftware();
        i.installJava();
    }
}

output

下載JAVA JDK
設定JAVA環境變數
確認JAVA版本
安裝完成!!

現在JavaJDK類別內的安裝的步驟downloadJDK和setEnv變成private,開放了一個install()供外部呼叫。如此一來即使要修改JAVA類別內安裝的步驟或邏輯,也不會影響到其他類別。

3. 是自己的就是自己的

一個方法,放在本類別沒問題,但放在其他類別也不會出錯。checkVer

import java.util.Random;

class JavaJDK {
    private Random rand = new Random(System.currentTimeMillis());

    private int downloadJDK(){
        System.out.println("下載JAVA JDK");
        return rand.nextInt(10); // 先用隨機數字的狀態表示狀態
    }
    private int setEnv(){
        System.out.println("設定JAVA環境變數");
        return rand.nextInt(10);
    }
    public int checkVer(){
        System.out.println("確認JAVA版本");
        return rand.nextInt(10);
    }
    public void install(){
        int jdkStatus = this.downloadJDK();
        if (jdkStatus != 0){
            int envStatus = this.setEnv();
            if (envStatus != 0){
                int verStatus = this.checkVer();
                if (verStatus != 0){
                    System.out.println("安裝完成!!");
                }
            }
        }
    }
    public void isInstalledJava(){
        int verStatus = this.checkVer();
        if (verStatus != 0){
            System.out.println("Java已安裝!!");
        } else {
            System.out.println("Java未完成!!");
        }
    }
}
class InstallSoftware {
    JavaJDK j = new JavaJDK();
    public void installJava(){
        this.j.install();
    }
    public void isInstalledJava(){
        int verStatus = this.j.checkVer();
        if (verStatus != 0){
            System.out.println("Java已安裝!!");
        } else {
            System.out.println("Java未完成!!");
        }
    }
}
public class MyComputer{
    public static void main(String args[]) {
        InstallSoftware i = new InstallSoftware();
        i.isInstalledJava();
    }
}

將第二點的範例做了一下更改,發現isInstalledJava()放在InstallSoftware類別內或是JavaJDK類別內都不會有問題。所以這個原則的意思就是:如果一個方法放在本類中,不增加類別間關係,也不對本類產生負面影響,那就可以放到本類中。

4. 謹慎使用Serializable

若使用不當,未來修改屬性,序列化時會拋異常NotSerializableException。

詳細可以參考這篇**謹慎實現Serializable介面**

  • 使用Law of Demeter


看完了有關LoD的解釋,這時來舉個例子讓我們更熟悉這個法則。

六度人脈空間這個名詞大家應該很熟悉,意指你和任何一個陌生人之間,只有六個人的距離。也就是說如果我想聯繫比爾蓋茲,我只要找到我與他之間的六個人,然後藉由這六個人的介紹,我就可以順利的聯繫到比爾蓋茲!!

這跟LoD有什麼關係?假設有一件事情我沒辦法獨力完成,我需要別人的幫助,而我周遭的朋友也沒有這個能力幫助我。但其中一個朋友認識比爾蓋茲,他剛好可以幫忙解決這件事。於是我就藉由我朋友中間牽的線,讓比爾蓋茲幫助我完成這件事。

class Me {
    private String name = "Me";
	public MyFriend getMyFriend() {
		return new MyFriend();
	}
	public void work() {
		MyFriend mf = getMyFriend();
		mf.getBillGates().work(name);
	}
}
class MyFriend {
    public BillGates getBillGates() {
        System.out.println("MyFriend介紹BillGates!!");
        return new BillGates();
    }
}
class BillGates {
    public void work(String name) {
		System.out.println("BillGates跟" + name + "握手!!");
	}
}
public class MyWorke {
    public static void main(String args[]) {
        Me me = new Me();
        me.work();
    }
}

output

MyFriend介紹BillGates!!
BillGates跟Me握手!!

最後比爾蓋茲確實幫我完成了一件事。但在class Me內,卻直接呼叫BillGates的work方法,這並不符合LoD。應該藉由MyFriend這個類別去呼叫BillGates做事才對。所以我們需要做一些更動

class Me {
    private String name = "Me";
	public MyFriend getMyFriend() {
		return new MyFriend();
	}
	public void work() {
		MyFriend mf = getMyFriend();
		mf.work(name);
	}
}
class MyFriend {
    public BillGates getBillGates() {
        return new BillGates();
    }
    public void work(String name) {
		BillGates bg = getBillGates();
        System.out.println("MyFriend介紹BillGates!!");
		bg.work(name);
	}
}
class BillGates {
    public void work(String name) {
		System.out.println("BillGates跟" + name + "握手!!");
	}
}
public class MyWorke {
    public static void main(String args[]) {
        Me me = new Me();
        me.work();
    }
}

output

MyFriend介紹BillGates!!
BillGates跟Me握手!!

經過修改後,我與比爾蓋茲沒有直接的關係,只需要跟MyFriend溝通,請他跟蓋茲牽線,就可以完成事情。

迪米特法則核心觀念就是解耦、弱耦合,只有弱耦合後,類別的複用率才可以提高。但解耦和也是要有限度的,過度的解耦會造成系統的複雜度提高,維護困難。

  • 小結


LoD定義
各單元對其他單元所知應當有限:只瞭解與目前單元最相關之單元
LoD核心觀念
解耦、弱耦合,提升類別複用率
LoD的優缺點
優點
1. 類別間的耦合度低
2. 提高模組獨立性
3. 提高類別的重複使用頻率及系統的擴張性
缺點
1. 產生大量的中介類別,系統複雜性提升
  • 範例程式碼


範例1:只和朋友交流
範例2:朋友間也應該有距離1
範例3:朋友間也應該有距離2
範例4:是自己的就是自己的
範例5:未使用LoD
範例6:使用LoD

  • References


軟體設計原則(六)迪米特法則 -Law of Demeter
设计模式六大原则 - 迪米特法则
迪米特法则
設計模式6大原則(5):迪米特法則


上一篇
[Day08] 合成/聚合複用原則 | Composite/Aggregate Reuse Principle
下一篇
[Day10] 單例模式 | Singleton Pattern
系列文
從生活中認識Design Pattern30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言