定義
其實《設計模式之禪》關於原則的部分,我最喜歡的就是《迪米特法則》沒什麼特殊原因,前段時間部門培訓,讓我出個培訓內容,就是選擇的迪米特法則。其實原因很簡單,就因爲看它名字都不知道究竟是拿來幹啥的。
先臭美一下《設計模式》——目錄,然後讓我們進入正題。
所以究竟什麼是迪米特法則呢?
迪米特法則(Law of Demeter,LoD)也稱爲最少知識原則(Least Knowledge Principle,LKP),雖然名字不同,但描述的是同一個規則:一個對象應該對其他對象有最少的瞭解。通俗地講,一個類應該對自己需要耦合或調用的類知道得最少,你(被耦合或調用的類)的內部是如何複雜都和我沒關係,那是你的事情,我就知道你提供的這麼多public方法,我就調用這麼多,其他的我一概不關心。
對於這個定義說實話我還是挺喜歡的,但也是生活中我很難做到的一點,那就是“我就調用這麼多,其他的我一概不關心”。其實在小的公司,這樣的性格是不好的,畢竟很難做到一個蘿蔔一個坑,大家或多或少都需要承擔一些職能外的任務。而且從個人成長的角度來說,其實也是參與越多,收穫也會越多。
有感一段廢話,可以跳過不看。職場中有一種情況,那就是A只做十件事,而且十件事都完成的很好;而B需要做一百件事,但是有一件事沒有做好。這兩個人誰的年終評估會更高一些呢?當然,這裏不是勸大家“各人自掃門前雪,莫管他人瓦上霜”(雖然,這是迪米特法則提倡的)。只是有這麼幾個建議:
- 能力範圍內,多自己有所成長的,能多參與還是多參與(不要超能力,不要超量)
- 既然參與了,能做好就盡己所能做到最好
- 如果一旦失敗了要用於承擔,實事求是說明情況,而不是抱怨於自己比別人多做了多少
- 當領導職能看到自己做錯的(不是隻說你錯了,畢竟確實有問題。要看其他相關福利待遇、boss對你的日常態度等方方面面),而不是作對的時,優先考慮一下自己的方法以及態度
- 如果如上都沒有問題,可以考慮換個舒服點的環境
當然,上面這麼多廢話,主要目的就是想說明,當我們的代碼不遵守迪米特法則的時候,一旦出現問題,要面臨這麼多的麻煩事。畢竟你作爲主角還能爲自己申辯一下,而我們可憐的代碼,只能被別人吐槽“這代碼真爛”,卻無力反抗啊。
而《設計模式之禪》中,迪米特法則對類的低耦合做出瞭如下四層定義:
- 只和朋友交流
- 朋友之間也是有距離的
- 是自己的就是自己的
- 謹慎使用Serializable
只和朋友交流
迪米特法則還有一個英文解釋是:Only talk to your immediate friends(只與直接的朋友通信。)什麼叫做直接的朋友呢?每個對象都必然會與其他對象有耦合關係,兩個對象之間的耦合就成爲朋友關係。
對於一個吃貨來說,這個時候必須要吃可樂雞翅了!
爲了吃可樂雞翅的,我們總共有兩種實現途徑:
- 自己做
- 去飯店
自己做
先說說自己做,最基本的,我們需要知道配料,還需要知道製作可樂雞翅的菜譜。當然,這些都不重要。作爲一個懶漢來說,我最怕的是還要去購買食材,買回來以後還要自己製作!
步驟:
- 把雞翅清洗乾淨,劃花刀。加入蠔油、醬油、鹽、料酒、姜,醃製雞翅。(天氣炎熱,最好放入冰箱,避免發臭。醃製半個小時左右)
- 在鍋里加入適量油,把醃製過後的雞翅放入鍋中兩面煎黃。
- 把剛纔醃製的調料一起倒入,加入可樂,大火燒開,然後收汁 !
- 健康又衛生的可樂雞翅就做好了!
以上可樂雞翅製作步驟來自下廚房
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提供的類除外。
朋友之間也是有距離的
人和人之間是有距離的,太遠關係逐漸疏遠,最終形同陌路;太近就相互刺傷。對朋友關係描述最貼切的故事就是:兩隻刺蝟取暖,太遠取不到暖,太近刺傷了對方,必須保持一個既能取暖又不刺傷對方的距離。迪米特法則就是對這個距離進行描述,即使是朋友類之間也不能無話不說,無所不知。
還是說我們的可樂雞翅,當美麗的小姐姐想要吃可樂雞翅的時候怎麼辦呢?還是兩個情況:
- 我們很熟
- 我們不熟
我們很熟
由於我對小姐姐比較熟悉,知道一些小姐姐的要求,在做可樂雞翅的時候自然需要考慮進去
- 可樂要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,訪問權限擴大了,如果服務器上沒有做出相應的變更,就會報序列化失敗,就這麼簡單。
對於這段話,我個人的理解是:“”