《設計模式》——迪米特法則

定義

  其實《設計模式之禪》關於原則的部分,我最喜歡的就是《迪米特法則》沒什麼特殊原因,前段時間部門培訓,讓我出個培訓內容,就是選擇的迪米特法則。其實原因很簡單,就因爲看它名字都不知道究竟是拿來幹啥的。
先臭美一下《設計模式》——目錄,然後讓我們進入正題。
  所以究竟什麼是迪米特法則呢?
什麼是迪米特法則

  迪米特法則(Law of Demeter,LoD)也稱爲最少知識原則(Least Knowledge Principle,LKP),雖然名字不同,但描述的是同一個規則:一個對象應該對其他對象有最少的瞭解。通俗地講,一個類應該對自己需要耦合或調用的類知道得最少,你(被耦合或調用的類)的內部是如何複雜都和我沒關係,那是你的事情,我就知道你提供的這麼多public方法,我就調用這麼多,其他的我一概不關心。

  對於這個定義說實話我還是挺喜歡的,但也是生活中我很難做到的一點,那就是“我就調用這麼多,其他的我一概不關心”。其實在小的公司,這樣的性格是不好的,畢竟很難做到一個蘿蔔一個坑,大家或多或少都需要承擔一些職能外的任務。而且從個人成長的角度來說,其實也是參與越多,收穫也會越多。
   有感一段廢話,可以跳過不看。職場中有一種情況,那就是A只做十件事,而且十件事都完成的很好;而B需要做一百件事,但是有一件事沒有做好。這兩個人誰的年終評估會更高一些呢?當然,這裏不是勸大家“各人自掃門前雪,莫管他人瓦上霜”(雖然,這是迪米特法則提倡的)。只是有這麼幾個建議:

  1. 能力範圍內,多自己有所成長的,能多參與還是多參與(不要超能力,不要超量)
  2. 既然參與了,能做好就盡己所能做到最好
  3. 如果一旦失敗了要用於承擔,實事求是說明情況,而不是抱怨於自己比別人多做了多少
  4. 當領導職能看到自己做錯的(不是隻說你錯了,畢竟確實有問題。要看其他相關福利待遇、boss對你的日常態度等方方面面),而不是作對的時,優先考慮一下自己的方法以及態度
  5. 如果如上都沒有問題,可以考慮換個舒服點的環境

   當然,上面這麼多廢話,主要目的就是想說明,當我們的代碼不遵守迪米特法則的時候,一旦出現問題,要面臨這麼多的麻煩事。畢竟你作爲主角還能爲自己申辯一下,而我們可憐的代碼,只能被別人吐槽“這代碼真爛”,卻無力反抗啊。
而《設計模式之禪》中,迪米特法則對類的低耦合做出瞭如下四層定義:

  1. 只和朋友交流
  2. 朋友之間也是有距離的
  3. 是自己的就是自己的
  4. 謹慎使用Serializable

只和朋友交流

   迪米特法則還有一個英文解釋是:Only talk to your immediate friends(只與直接的朋友通信。)什麼叫做直接的朋友呢?每個對象都必然會與其他對象有耦合關係,兩個對象之間的耦合就成爲朋友關係。

   對於一個吃貨來說,這個時候必須要吃可樂雞翅了!
可樂雞翅
   爲了吃可樂雞翅的,我們總共有兩種實現途徑:

  1. 自己做
  2. 去飯店

自己做

   先說說自己做,最基本的,我們需要知道配料,還需要知道製作可樂雞翅的菜譜。當然,這些都不重要。作爲一個懶漢來說,我最怕的是還要去購買食材,買回來以後還要自己製作!

配料
   步驟:

  1. 把雞翅清洗乾淨,劃花刀。加入蠔油、醬油、鹽、料酒、姜,醃製雞翅。(天氣炎熱,最好放入冰箱,避免發臭。醃製半個小時左右)
  2. 在鍋里加入適量油,把醃製過後的雞翅放入鍋中兩面煎黃。
  3. 把剛纔醃製的調料一起倒入,加入可樂,大火燒開,然後收汁 !
  4. 健康又衛生的可樂雞翅就做好了!

   以上可樂雞翅製作步驟來自下廚房

public class Blog {
    @Test
    public void order() {
        MySelf mySelf = new MySelf();
        mySelf.order(new Waitress());
    }
}

class ColaChickenWings {
    private ChickenWings chickenWings;
    private Cola cola;

    public boolean cooked() {
        return 0 == new Random().nextInt(5);
    }

    public ChickenWings getChickenWings() {
        return chickenWings;
    }

    public void setChickenWings(ChickenWings chickenWings) {
        this.chickenWings = chickenWings;
    }

    public Cola getCola() {
        return cola;
    }

    public void setCola(Cola cola) {
        this.cola = cola;
    }

    /**
     * 雞翅
     */
    class ChickenWings {

    }

    /**
     * 可樂
     */
    class Cola {

    }
}

class MySelf {
    public void order(Waitress waitress) {
        ColaChickenWings colaChickenWings = new ColaChickenWings();
        //  買雞翅
        if (null == colaChickenWings.getChickenWings()) {
            colaChickenWings.setChickenWings(colaChickenWings.new ChickenWings());
        }
        //  買可樂
        if (null == colaChickenWings.getCola()) {
            colaChickenWings.setCola(colaChickenWings.new Cola());
        }
        waitress.orderColaChickenWings(colaChickenWings);
    }
}

class Waitress {
    public void orderColaChickenWings(ColaChickenWings colaChickenWings) {
        if (null != colaChickenWings) {
            while (true) {
                if (colaChickenWings.cooked()) {
                    System.out.println("美味的可樂雞翅出鍋嘍!!!");
                    break;
                } else {
                    System.out.println("馬上就好了");
                }
            }
        }
    }
}

上菜

去飯店

   說到去飯店,這個步驟就複雜了,我們到飯店以後還要高喊一聲:
來份可樂雞翅
   之後只需要等上那麼一會,可愛的服務員小姐姐就把可樂雞翅端到我面前來了!!!驚不驚喜?意不意外?

  顯而易見,去飯店這種選擇就符合了我們的最小知識原則,而我們只需要與可愛的服務員小姐姐成爲朋友,她就會點好菜,再給你端上來就好了,而不需要我們去了解可樂雞翅那麼繁瑣的製作過程。

public class Blog {
    @Test
    public void order() {
        MySelf mySelf = new MySelf();
        mySelf.order(new Waitress());
    }
}

class ColaChickenWings {
    private ChickenWings chickenWings;
    private Cola cola;

    public boolean cooked() {
        // 買雞翅
        if (null == chickenWings) {
            chickenWings = new ChickenWings();
        }
        // 買可樂
        if (null == cola) {
            cola = new Cola();
        }
        return 0 == new Random().nextInt(5);
    }

    /**
     * 雞翅
     */
    class ChickenWings {

    }

    /**
     * 可樂
     */
    class Cola {

    }
}

class MySelf {
    public void order(Waitress waitress) {
        waitress.orderColaChickenWings();
    }
}

class Waitress {
    public void orderColaChickenWings() {
        ColaChickenWings colaChickenWings = new ColaChickenWings();
        while (true) {
            if (colaChickenWings.cooked()) {
                System.out.println("美味的可樂雞翅出鍋嘍!!!");
                break;
            } else {
                System.out.println("馬上就好了");
            }
        }
    }
}

上菜
  當然,這裏還是需要注意一點,那就是雖然老話說“朋友的朋友是朋友”,但是你找朋友的朋友借個錢試試?

  一個類只和朋友交流,不與陌生類交流,不要出現getA().getB().getC().getD()這種情況(在一種極端的情況下允許出現這種訪問,即每一個點號後面的返回類型都相同),類與類之間的關係是建立在類間的,而不是方法間,因此一個方法儘量不引入一個類中不存在的對象,當然,JDK API提供的類除外。

鏈式調用

朋友之間也是有距離的

  人和人之間是有距離的,太遠關係逐漸疏遠,最終形同陌路;太近就相互刺傷。對朋友關係描述最貼切的故事就是:兩隻刺蝟取暖,太遠取不到暖,太近刺傷了對方,必須保持一個既能取暖又不刺傷對方的距離。迪米特法則就是對這個距離進行描述,即使是朋友類之間也不能無話不說,無所不知。

朋友之間也是有距離的
  還是說我們的可樂雞翅,當美麗的小姐姐想要吃可樂雞翅的時候怎麼辦呢?還是兩個情況:

  1. 我們很熟
  2. 我們不熟

我們很熟

  由於我對小姐姐比較熟悉,知道一些小姐姐的要求,在做可樂雞翅的時候自然需要考慮進去

  • 可樂要0卡的
  • 小姐姐討厭蒜的味道
  • 小姐姐喜歡吃鮮雞翅,不能是凍的
  • 小姐姐減肥,不能放太多油
  • 小姐姐……

都需要考慮

public class Blog {
    @Test
    public void order() {
        MySelf mySelf = new MySelf();
        mySelf.cookColaChickenWings(new CuteGirl());
    }
}

class ColaChickenWings {
    private Calorie calorie = new Calorie();
    private Garlic garlic = new Garlic();

    public Calorie getCalorie() {
        return calorie;
    }

    public void setCalorie(Calorie calorie) {
        this.calorie = calorie;
    }

    public Garlic getGarlic() {
        return garlic;
    }

    public void setGarlic(Garlic garlic) {
        this.garlic = garlic;
    }

    class Calorie {

    }

    class Garlic {

    }
}

class MySelf {
    public void cookColaChickenWings(CuteGirl cuteGirl) {
        ColaChickenWings colaChickenWings = new ColaChickenWings();
        if (null != colaChickenWings.getCalorie()) {
            colaChickenWings.setCalorie(null);
        }

        if (null != colaChickenWings.getGarlic()) {
            colaChickenWings.setGarlic(null);
        }

        cuteGirl.eat(colaChickenWings);
    }
}

class CuteGirl {
    public void eat(ColaChickenWings colaChickenWings) {
        if (null != colaChickenWings.getGarlic() || null != colaChickenWings.getCalorie()) {
            System.out.println("不好吃,再也不理你了");
        }
    }
}

我們不熟

  那還管什麼,我還不想怎麼做怎麼做啊!
味道怎麼樣?

public class Blog {
    @Test
    public void order() {
        MySelf mySelf = new MySelf();
        mySelf.cookColaChickenWings(new CuteGirl());
    }
}

class ColaChickenWings {
    private Calorie calorie = new Calorie();
    private Garlic garlic = new Garlic();

    public Calorie getCalorie() {
        return calorie;
    }

    public void setCalorie(Calorie calorie) {
        this.calorie = calorie;
    }

    public Garlic getGarlic() {
        return garlic;
    }

    public void setGarlic(Garlic garlic) {
        this.garlic = garlic;
    }

    class Calorie {

    }

    class Garlic {

    }
}

class MySelf {
    public void cookColaChickenWings(CuteGirl cuteGirl) {
        cuteGirl.eat(new ColaChickenWings());
    }
}

class CuteGirl {
    public void eat(ColaChickenWings colaChickenWings) {
        if (null != colaChickenWings.getGarlic()) {
            colaChickenWings.setGarlic(null);
        }
        if (null != colaChickenWings.getCalorie()) {
            colaChickenWings.setCalorie(null);
        }
        System.out.println("味道還行");
    }
}

  看看,是不是省心了很多(是不是注孤生不在考慮範圍內)

  一個類公開的public屬性或非法越多,修改時涉及的面也就越大,變更引起的風險擴散也就越大。因此,爲了保持朋友類間的距離,在設計時需要反覆衡量:是否還可以再減少public方法和屬性,是否可以修改爲private、package-private(包類型,在類、方法、變量前不加訪問權限,則默認爲包類型)、protected等訪問權限,是否可以加上final關鍵字等。
  迪米特法則要求類“羞澀”一點,儘量不要對外公佈太多的public方法和非靜態的public變量,儘量內斂,多使用private、package-private、protected等訪問權限。

是自己的就是自己的

  這一條又說明了什麼?可樂雞翅還是要會做的!畢竟就算飯店倒閉了,就算小姐姐離開了我,至少我還是有可樂雞翅吃的!
我要吃雞翅

  在實際應用中經常會出現這樣一個方法:放在本類中也可以,放在其他類中也沒有錯,那怎麼去衡量呢?你可以堅持這樣一個原則:如果一個方法放在本類中,既不增加類間關係,也對本類不產生負面影響,那就放置在本類中。

  而從前面的例子也可以看得出來,實際就是儘可能將類自身能夠完成的判斷、流程等,在該類內部完成,只對外提供一個調取的public即可,而不是將這些都暴露在外,讓調用的類自己去判斷(比如點菜,和陌生小姐姐吃可樂雞翅)。

謹慎使用Serializable

  好吧,又冒出來了一個新名詞Serializable,它究竟是什麼意思呢?

  序列化 (Serialization)是將對象的狀態信息轉換爲可以存儲或傳輸的形式的過程。在序列化期間,對象將其當前狀態寫入到臨時或持久性存儲區。以後,可以通過從存儲區中讀取或反序列化對象的狀態,重新創建該對象。

  好了,下面來看看在《設計模式之禪》中關於這部分的介紹:

  在一個項目中使用RMI(Remote Method Invocation,遠程方法調用)方式傳遞一個VO(Value Object,值對象),這個對象就必須實現Serializable接口(僅僅是一個標誌性接口,不需要實現具體的方法),也就是把需要網絡傳輸的對象進行序列化,否則就會出現NotSerializableException異常。突然有一天,客戶端的VO修改了一個屬性的訪問權限,從private變更爲public,訪問權限擴大了,如果服務器上沒有做出相應的變更,就會報序列化失敗,就這麼簡單。

  對於這段話,我個人的理解是:“\color{red}{能序列化就序列化,不序列化可能會掛!}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章