六大設(shè)計原則(設(shè)計模式之禪讀書筆記)

[TOC]

單一設(shè)計原則---(專人專事)

單一職責原則的定義是:應(yīng)該有且僅有一個原因引起類的變更。

image.png

優(yōu)化:
重新拆封成兩個接口,IUserBO負責用戶的屬性,簡單地說,IUserBO的職責就是收集和 反饋用戶的屬性信息;IUserBiz負責用戶的行為,完成用戶信息的維護和變更。

image.png
image.png

簡單理解就是 get set 一起,其他操作封裝成biz(biz是Business的縮寫,實際上就是控制層(業(yè)務(wù)邏輯層)),當然不局限于這種類型的對象。

注意 單一職責原則提出了一個編寫程序的標準,用“職責”或“變化原因”來衡量接口或 類設(shè)計得是否優(yōu)良,但是“職責”和“變化原因”都是不可度量的,因項目而異,因環(huán)境而異

單一職責適用于接口、類,同時也適用于方法,什么意思呢?一個方法盡可能做一件事 情,比如一個方法修改用戶密碼,不要把這個方法放到“修改用戶信息”方法中,這個方法的 顆粒度很粗。
如果要修改用戶名稱,就調(diào)用changeUserName方法;要修改家庭地址, 就調(diào)用changeHomeAddress方法;要修改單位電話,就調(diào)用changeOfficeTel方法。每個方法的 職責非常清晰明確,不僅開發(fā)簡單,而且日后的維護也非常容易,大家可以逐漸養(yǎng)成這樣的 習慣。

這個單一原則重在理解,不能認死理,拆分太嚴重也會導致類或者方法數(shù)過多,具體情況具體分析吧。

里氏替換原則---(繼承規(guī)范)

主要是為良好的繼承定義了一個規(guī)范。

這個規(guī)范就是:所有引用基類的地方必須能透明地使用其子類的對象(不會改變?nèi)魏芜壿嫞?br> 通俗點講,只要父類能出現(xiàn)的地方子類就可以出現(xiàn),而且 替換為子類也不會產(chǎn)生任何錯誤或異常,使用者可能根本就不需要知道是父類還是子類。但 是,反過來就不行了,有子類出現(xiàn)的地方,父類未必就能適應(yīng)。
更正宗的定義:如果對每一個類型為S的對象o1,都有類型為T的對 象o2,使得以T定義的所有程序P在所有的對象o1都代換成o2時,程序P的行為沒有發(fā)生變 化,那么類型S是類型T的子類型。

說明:這是給繼承定義的一種良好的規(guī)范,現(xiàn)實中可能會出現(xiàn)不符合這種原則的代碼,所以這是規(guī)范,并不是所有都是這樣的。

細分里氏替換原則四種含義:

  • 子類必須完全實現(xiàn)父類的方法(這里的實現(xiàn)應(yīng)該是要保證都有方法體的意思)
    比如說父類是個槍,定義了一個方法“射擊”,那么任意子類(玩具槍、狙擊槍、步槍)都應(yīng)該能調(diào)用射擊這個方法。
  • 子類可以有自己的個性
    當然子類可以增加一些方法或者復寫一些方法
  • 覆蓋或?qū)崿F(xiàn)父類的方法時輸入?yún)?shù)可以被放大
    范例:
public class Father {
 public Collection doSomething(HashMap map){
   System.out.println("父類被執(zhí)行...");
   return map.values();
 }
}
public class Son extends Father {
  //放大輸入?yún)?shù)類型
  public Collection doSomething(Map map){
    System.out.println("子類被執(zhí)行...");
    return map.values();
  }
}

請注意粗體部分,與父類的方法名相同,但又不是覆寫(Override)父類的方法。你加 個@Override試試看,會報錯的,為什么呢?方法名雖然相同,但方法的輸入?yún)?shù)不同,就 不是覆寫,那這是什么呢?是重載(Overload)!不用大驚小怪的,不在一個類就不能是重 載了?繼承是什么意思,子類擁有父類的所有屬性和方法,方法名相同,輸入?yún)?shù)類型又不 相同,當然是重載了。

父類使用場景:

public class Client {
   public static void invoker(){
      //父類存在的地方,子類就應(yīng)該能夠存在
      Father f = new Father();
      HashMap map = new HashMap();
      f.doSomething(map);
   }
   public static void main(String[] args) {
      invoker();
   }
}

運行結(jié)果:父類被執(zhí)行...
根據(jù)里氏替換原則在這里使用子類替換:

public class Client {
   public static void invoker(){
      //父類存在的地方,子類就應(yīng)該能夠存在
      Son f =new Son();
      HashMap map = new HashMap();
      f.doSomething(map);
   }
   public static void main(String[] args) {
      invoker();
   }
}

運行結(jié)果:父類被執(zhí)行...
運行結(jié)果還是一樣,看明白是怎么回事了嗎?父類方法的輸入?yún)?shù)是HashMap類型,子 類的輸入?yún)?shù)是Map類型,也就是說子類的輸入?yún)?shù)類型的范圍擴大了,子類代替父類傳遞 到調(diào)用者中,子類的方法永遠都不會被執(zhí)行。這是正確的,如果你想讓子類的方法運行,就 必須覆寫父類的方法。

對調(diào)參數(shù):

public class Father {
 public Collection doSomething(Map map){
   System.out.println("父類被執(zhí)行...");
   return map.values();
 }
}
public class Son extends Father {
  //放大輸入?yún)?shù)類型
  public Collection doSomething(HashMap map){
    System.out.println("子類被執(zhí)行...");
    return map.values();
  }
}

如果依然使用上面的使用場景運行結(jié)果就會變成這樣:
運行結(jié)果:父類被執(zhí)行...
更換子類
運行結(jié)果:子類被執(zhí)行...

這就不正常了,子類在沒有覆寫父類的方法的前提下,子類方法被執(zhí)行了,這會引起業(yè)務(wù) 邏輯混亂 ,“歪曲”了父類的意圖,引起一堆意想不到的業(yè)務(wù)邏輯混亂,所以子類中方法的前置條 件必須與超類中被覆寫的方法的前置條件相同或者更寬松。

  • 覆寫或?qū)崿F(xiàn)父類的方法時輸出結(jié)果可以被縮小
    這是什么意思呢,父類的一個方法的返回值是一個類型F,子類的相同方法(重載或覆 寫)的返回值為S,那么里氏替換原則就要求S必須小于等于F,也就是說,要么S和F是同一 個類型,要么S是F的子類,為什么呢?分兩種情況,如果是覆寫,父類和子類的同名方法的 輸入?yún)?shù)是相同的,兩個方法的范圍值S小于等于F,這是覆寫的要求,這才是重中之重,子 類覆寫父類的方法,天經(jīng)地義。如果是重載,則要求方法的輸入?yún)?shù)類型或數(shù)量不相同,在 里氏替換原則要求下,就是子類的輸入?yún)?shù)寬于或等于父類的輸入?yún)?shù),也就是說你寫的這 個方法是不會被調(diào)用的,參考上面講的前置條件。

綜合3、4條可以總結(jié)一句話:子類入?yún)㈩愋涂梢苑糯蠓秶梢允歉溉雲(yún)⒌母割悾敵鼋Y(jié)果要縮小范圍(可是父出參的子類)。
理解起來比較困難。

依賴倒置原則---(面向接口編程)

  • 高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴其抽象;
  • 抽象不應(yīng)該依賴細節(jié);
  • 細節(jié)應(yīng)該依賴抽象。
    高層模塊和低層模塊容易理解,每一個邏輯的實現(xiàn)都是由原子邏輯組成的,不可分割的 原子邏輯就是低層模塊,原子邏輯的再組裝就是高層模塊。那什么是抽象?什么又是細節(jié) 呢?在Java語言中,抽象就是指接口或抽象類,兩者都是不能直接被實例化的;細節(jié)就是實 現(xiàn)類,實現(xiàn)接口或繼承抽象類而產(chǎn)生的類就是細節(jié),其特點就是可以直接被實例化,也就是 可以加上一個關(guān)鍵字new產(chǎn)生一個對象。
    依賴倒置原則在Java語言中的表現(xiàn)就是:
  • 模塊間的依賴通過抽象發(fā)生,實現(xiàn)類之間不發(fā)生直接的依賴關(guān)系,其依賴關(guān)系是通過 接口或抽象類產(chǎn)生的;
  • 接口或抽象類不依賴于實現(xiàn)類;
  • 實現(xiàn)類依賴接口或抽象類。
    更加精簡的定義就是“面向接口編程”——OOD(Object-Oriented Design,面向?qū)ο笤O(shè) 計)的精髓之一。

舉個例子:
司機開動奔馳車:

image.png

這樣設(shè)計的話突然來個寶馬,司機沒有對應(yīng)開寶馬的方法,就不能執(zhí)行了。
司機類和奔馳車類之間是緊耦合的關(guān)系,其導致的結(jié)果就是系統(tǒng)的可維護性大大降低。

對上面的例子進行優(yōu)化,引入依賴倒置 原則后的類圖如圖3-2所示

image.png

建立兩個接口:IDriver和ICar,分別定義了司機和汽車的各個職能,司機就是駕駛汽 車,必須實現(xiàn)drive()方法

在業(yè)務(wù)場景中,我們貫徹“抽象不應(yīng)該依賴細節(jié)”,也就是我們認為抽象(ICar接口)不 依賴BMW和Benz兩個實現(xiàn)類(細節(jié)),因此在高層次的模塊中應(yīng)用都是抽象
代碼如下:

    public interface ICar {
        //是汽車就應(yīng)該能跑
        public void run();
    }
    public class Benz implements ICar{
        //汽車肯定會跑
        public void run(){
            System.out.println("奔馳汽車開始運行...");
        }
    }
    public class BMW implements ICar{
        //寶馬車當然也可以開動了
        public void run(){
            System.out.println("寶馬汽車開始運行...");
        }
    }
  public class Driver implements IDriver{
        //司機的主要職責就是駕駛汽車
        public void drive(ICar car){
            car.run();
        }
    }
  • 接口和抽象類都是屬于抽象的,有了抽象才可能依賴倒置。
  • 變量的表面類型盡量是接口或者是抽象類

講了這么多,估計大家對“倒置”這個詞還是有點不理解,那到底什么是“倒置”呢?我們 先說“正置”是什么意思,依賴正置就是類間的依賴是實實在在的實現(xiàn)類間的依賴,也就是面 向?qū)崿F(xiàn)編程,這也是正常人的思維方式,我要開奔馳車就依賴奔馳車,我要使用筆記本電腦 就直接依賴筆記本電腦,而編寫程序需要的是對現(xiàn)實世界的事物進行抽象,抽象的結(jié)果就是 有了抽象類和接口,然后我們根據(jù)系統(tǒng)設(shè)計的需要產(chǎn)生了抽象間的依賴,代替了人們傳統(tǒng)思 維中的事物間的依賴,“倒置”就是從這里產(chǎn)生的。

接口隔離原則---(定義接口規(guī)范)

  1. 客戶端不應(yīng)該依 賴它不需要的接口

依賴它需要的接口,客 戶端需要什么接口就提供什么接口,把不需要的接口剔除掉,那就需要對接口進行細化,保 證其純潔性

  1. 類間的依賴關(guān)系應(yīng)該建立在最小的接口上

它要求是最小 的接口,也是要求接口細化,接口純潔,與第一個定義如出一轍,只是一個事物的兩種不同 描述。

總結(jié)上面兩句話:

  • 建立單一接口,不要建立臃腫龐大的接口。再通 俗一點講:接口盡量細化,同時接口中的方法盡量少。
    ** 看到這里大家有可能要疑惑了,這與 單一職責原則不是相同的嗎?錯,接口隔離原則與單一職責的審視角度是不相同的,單一職 責要求的是類和接口職責單一,注重的是職責,這是業(yè)務(wù)邏輯上的劃分,而接口隔離原則要 求接口的方法盡量少。例如一個接口的職責可能包含10個方法,這10個方法都放在一個接口 中,并且提供給多個模塊訪問,各個模塊按照規(guī)定的權(quán)限來訪問,在系統(tǒng)外通過文檔約 束“不使用的方法不要訪問”,按照單一職責原則是允許的,按照接口隔離原則是不允許的, 因為它要求“盡量使用多個專門的接口”。專門的接口指什么?就是指提供給每個模塊的都應(yīng) 該是單一接口,提供給幾個模塊就應(yīng)該有幾個接口,而不是建立一個龐大的臃腫的接口,容 納所有的客戶端訪問。**

實際應(yīng)用上面就是一個接口里面不要寫太多方法,如果確實需要很多方法的話,應(yīng)該盡量根據(jù)實際需求進行拆分,拆分成多個接口,按需實現(xiàn)。

例如:對美女的定義 面貌、身材和氣質(zhì) 定義了如下接口

image.png

然而每個人的審美觀不同,并不是所有的人都認為美女都是這三種條件的
比如唐朝 身材就認為胖點的好
所以接口要進行拆分,按需進行實現(xiàn)

image.png

接口是我們設(shè)計時對外 提供的契約,通過分散定義多個接口,可以預防未來變更的擴散,提高系統(tǒng)的靈活性和可維 護性。

根據(jù)接口隔離原則拆分接口時,首先必須滿足單一職責原則。

迪米特法則---(低耦合)

對類的低耦合提出了明確的要求

  1. 只和朋友交流
    老師想讓體育委員確認一下全班女生來齊沒有,就對他 說:“你去把全班女生清一下。
image.png
image.png

場景類:

image.png

首先確定Teacher類有幾個朋友類,它僅有一個朋友類—— GroupLeader。為什么Girl不是朋友類呢?Teacher也對它產(chǎn)生了依賴關(guān)系呀!朋友類的定義是 這樣的:出現(xiàn)在成員變量、方法的輸入輸出參數(shù)中的類稱為成員朋友類,而出現(xiàn)在方法體內(nèi) 部的類不屬于朋友類,而Girl這個類就是出現(xiàn)在commond方法體內(nèi),因此不屬于Teacher類的 朋友類。迪米特法則告訴我們一個類只和朋友類交流,但是我們剛剛定義的commond方法卻 與Girl類有了交流,聲明了一個List動態(tài)數(shù)組,也就是與一個陌生的類Girl有了交流, 這樣就破壞了Teacher的健壯性。方法是類的一個行為,類竟然不知道自己的行為與其他類 產(chǎn)生依賴關(guān)系,這是不允許的,嚴重違反了迪米特法則。

所以應(yīng)該修改調(diào)整一下:

image.png
image.png

場景類:

image.png

對程序進行了簡單的修改,把Teacher中對List的初始化移動到了場景類中,同時 在GroupLeader中增加了對Girl的注入,避開了Teacher類對陌生類Girl的訪問,降低了系統(tǒng)間 的耦合,提高了系統(tǒng)的健壯性。

  • 迪米特法則要求類“羞澀”一點,盡量不要對外公布太多的public方法和非靜態(tài)的 public變量,盡量內(nèi)斂,多使用private、package-private、protected等訪問權(quán)限。

比如說把大象裝冰箱需要三步,任何一步失敗都會導致接下來的動作無法執(zhí)行,我們應(yīng)該封裝一個把大象裝冰箱的方法(涵蓋這三步)開放出去,而不應(yīng)該把這三步都開放出去。

  • 迪米特法則的核心觀念就是類間解耦,弱耦合,只有弱耦合了以后,類的復用率才可以 提高。其要求的結(jié)果就是產(chǎn)生了大量的中轉(zhuǎn)或跳轉(zhuǎn)類,導致系統(tǒng)的復雜性提高,同時也為維 護帶來了難度。讀者在采用迪米特法則時需要反復權(quán)衡,既做到讓結(jié)構(gòu)清晰,又做到高內(nèi)聚 低耦合。

開閉原則---(開放擴展,關(guān)閉修改)

  • 定義:一個軟件實體如類、模塊和函數(shù)應(yīng)該對擴展開放,對修改關(guān)閉。

開閉原則的定義已經(jīng)非常明確地告訴我們:軟件實體應(yīng)該對擴展開放,對修改關(guān)閉,其 含義是說一個軟件實體應(yīng)該通過擴展來實現(xiàn)變化,而不是通過修改已有的代碼來實現(xiàn)變化

  • 比如說原來賣書是原件賣,現(xiàn)在突然要打折了,如果改變原有邏輯可能會導致出現(xiàn)各種問題,工作量也大,應(yīng)該找一種安全的方案小范圍改動,比如繼承
  • 再比如說,平常工作遇到一個類需要增加個新功能,在原有類的基礎(chǔ)上改動一下就可以解決,當時覺著沒問題,后來可能就會引發(fā)一系列問題,因為這個類已經(jīng)被很多其他類引用著,直接修改的話非常容易引起一些意想不到的問題。這時候就應(yīng)該使用擴展方式來實現(xiàn)我們想要的新功能,比如可以新增方法,或者使用繼承,這樣擴展,既可以保證我們對新需求的實現(xiàn),又可以避免直接修改方法帶來的一系列問題。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內(nèi)容