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

[TOC]

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

單一職責原則的定義是:應該有且僅有一個原因引起類的變更锦针。

image.png

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

image.png
image.png

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

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

單一職責適用于接口褥琐、類,同時也適用于方法晤郑,什么意思呢敌呈?一個方法盡可能做一件事 情,比如一個方法修改用戶密碼造寝,不要把這個方法放到“修改用戶信息”方法中磕洪,這個方法的 顆粒度很粗。
如果要修改用戶名稱诫龙,就調用changeUserName方法析显;要修改家庭地址, 就調用changeHomeAddress方法签赃;要修改單位電話谷异,就調用changeOfficeTel方法分尸。每個方法的 職責非常清晰明確,不僅開發(fā)簡單歹嘹,而且日后的維護也非常容易箩绍,大家可以逐漸養(yǎng)成這樣的 習慣。

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

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

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

這個規(guī)范就是:所有引用基類的地方必須能透明地使用其子類的對象(不會改變任何邏輯)
通俗點講,只要父類能出現(xiàn)的地方子類就可以出現(xiàn)抽诉,而且 替換為子類也不會產生任何錯誤或異常陨簇,使用者可能根本就不需要知道是父類還是子類。但 是迹淌,反過來就不行了河绽,有子類出現(xiàn)的地方,父類未必就能適應唉窃。
更正宗的定義:如果對每一個類型為S的對象o1耙饰,都有類型為T的對 象o2,使得以T定義的所有程序P在所有的對象o1都代換成o2時纹份,程序P的行為沒有發(fā)生變 化苟跪,那么類型S是類型T的子類型。

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

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

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

請注意粗體部分鸠姨,與父類的方法名相同铜秆,但又不是覆寫(Override)父類的方法。你加 個@Override試試看讶迁,會報錯的连茧,為什么呢?方法名雖然相同,但方法的輸入參數(shù)不同梅屉,就 不是覆寫值纱,那這是什么呢?是重載(Overload)坯汤!不用大驚小怪的虐唠,不在一個類就不能是重 載了?繼承是什么意思惰聂,子類擁有父類的所有屬性和方法疆偿,方法名相同,輸入參數(shù)類型又不 相同搓幌,當然是重載了杆故。

父類使用場景:

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

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

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

運行結果:父類被執(zhí)行...
運行結果還是一樣处铛,看明白是怎么回事了嗎?父類方法的輸入參數(shù)是HashMap類型拐揭,子 類的輸入參數(shù)是Map類型撤蟆,也就是說子類的輸入參數(shù)類型的范圍擴大了,子類代替父類傳遞 到調用者中堂污,子類的方法永遠都不會被執(zhí)行家肯。這是正確的,如果你想讓子類的方法運行盟猖,就 必須覆寫父類的方法讨衣。

對調參數(shù):

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

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

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

  • 覆寫或實現(xiàn)父類的方法時輸出結果可以被縮小
    這是什么意思呢价说,父類的一個方法的返回值是一個類型F,子類的相同方法(重載或覆 寫)的返回值為S风秤,那么里氏替換原則就要求S必須小于等于F鳖目,也就是說,要么S和F是同一 個類型缤弦,要么S是F的子類领迈,為什么呢?分兩種情況,如果是覆寫狸捅,父類和子類的同名方法的 輸入參數(shù)是相同的衷蜓,兩個方法的范圍值S小于等于F,這是覆寫的要求尘喝,這才是重中之重磁浇,子 類覆寫父類的方法,天經地義朽褪。如果是重載置吓,則要求方法的輸入參數(shù)類型或數(shù)量不相同,在 里氏替換原則要求下缔赠,就是子類的輸入參數(shù)寬于或等于父類的輸入參數(shù)衍锚,也就是說你寫的這 個方法是不會被調用的,參考上面講的前置條件嗤堰。

綜合3戴质、4條可以總結一句話:子類入參類型可以放大范圍(可以是父入參的父類),輸出結果要縮小范圍(可是父出參的子類)踢匣。
理解起來比較困難告匠。

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

  • 高層模塊不應該依賴低層模塊,兩者都應該依賴其抽象符糊;
  • 抽象不應該依賴細節(jié)凫海;
  • 細節(jié)應該依賴抽象。
    高層模塊和低層模塊容易理解男娄,每一個邏輯的實現(xiàn)都是由原子邏輯組成的行贪,不可分割的 原子邏輯就是低層模塊,原子邏輯的再組裝就是高層模塊模闲。那什么是抽象建瘫?什么又是細節(jié) 呢?在Java語言中尸折,抽象就是指接口或抽象類啰脚,兩者都是不能直接被實例化的;細節(jié)就是實 現(xiàn)類实夹,實現(xiàn)接口或繼承抽象類而產生的類就是細節(jié)橄浓,其特點就是可以直接被實例化,也就是 可以加上一個關鍵字new產生一個對象亮航。
    依賴倒置原則在Java語言中的表現(xiàn)就是:
  • 模塊間的依賴通過抽象發(fā)生荸实,實現(xiàn)類之間不發(fā)生直接的依賴關系,其依賴關系是通過 接口或抽象類產生的缴淋;
  • 接口或抽象類不依賴于實現(xiàn)類准给;
  • 實現(xiàn)類依賴接口或抽象類泄朴。
    更加精簡的定義就是“面向接口編程”——OOD(Object-Oriented Design,面向對象設 計)的精髓之一露氮。

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

image.png

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

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

image.png

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

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

    public interface ICar {
        //是汽車就應該能跑
        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)類間的依賴纠吴,也就是面 向實現(xiàn)編程,這也是正常人的思維方式慧瘤,我要開奔馳車就依賴奔馳車戴已,我要使用筆記本電腦 就直接依賴筆記本電腦,而編寫程序需要的是對現(xiàn)實世界的事物進行抽象锅减,抽象的結果就是 有了抽象類和接口糖儡,然后我們根據系統(tǒng)設計的需要產生了抽象間的依賴,代替了人們傳統(tǒng)思 維中的事物間的依賴怔匣,“倒置”就是從這里產生的握联。

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

  1. 客戶端不應該依 賴它不需要的接口

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

  1. 類間的依賴關系應該建立在最小的接口上

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

總結上面兩句話:

  • 建立單一接口,不要建立臃腫龐大的接口荞膘。再通 俗一點講:接口盡量細化罚随,同時接口中的方法盡量少。
    ** 看到這里大家有可能要疑惑了羽资,這與 單一職責原則不是相同的嗎淘菩?錯,接口隔離原則與單一職責的審視角度是不相同的屠升,單一職 責要求的是類和接口職責單一潮改,注重的是職責,這是業(yè)務邏輯上的劃分腹暖,而接口隔離原則要 求接口的方法盡量少汇在。例如一個接口的職責可能包含10個方法,這10個方法都放在一個接口 中脏答,并且提供給多個模塊訪問糕殉,各個模塊按照規(guī)定的權限來訪問,在系統(tǒng)外通過文檔約 束“不使用的方法不要訪問”殖告,按照單一職責原則是允許的阿蝶,按照接口隔離原則是不允許的, 因為它要求“盡量使用多個專門的接口”黄绩。專門的接口指什么羡洁?就是指提供給每個模塊的都應 該是單一接口,提供給幾個模塊就應該有幾個接口爽丹,而不是建立一個龐大的臃腫的接口筑煮,容 納所有的客戶端訪問。**

實際應用上面就是一個接口里面不要寫太多方法习劫,如果確實需要很多方法的話咆瘟,應該盡量根據實際需求進行拆分,拆分成多個接口诽里,按需實現(xiàn)袒餐。

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

image.png

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

image.png

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

根據接口隔離原則拆分接口時捕仔,首先必須滿足單一職責原則匕积。

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

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

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

場景類:

image.png

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

所以應該修改調整一下:

image.png
image.png

場景類:

image.png

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

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

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

  • 迪米特法則的核心觀念就是類間解耦狐赡,弱耦合,只有弱耦合了以后疟丙,類的復用率才可以 提高颖侄。其要求的結果就是產生了大量的中轉或跳轉類鸟雏,導致系統(tǒng)的復雜性提高,同時也為維 護帶來了難度览祖。讀者在采用迪米特法則時需要反復權衡崔慧,既做到讓結構清晰,又做到高內聚 低耦合穴墅。

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

  • 定義:一個軟件實體如類温自、模塊和函數(shù)應該對擴展開放玄货,對修改關閉。

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

  • 比如說原來賣書是原件賣馆里,現(xiàn)在突然要打折了隘世,如果改變原有邏輯可能會導致出現(xiàn)各種問題,工作量也大鸠踪,應該找一種安全的方案小范圍改動丙者,比如繼承
  • 再比如說,平常工作遇到一個類需要增加個新功能营密,在原有類的基礎上改動一下就可以解決械媒,當時覺著沒問題,后來可能就會引發(fā)一系列問題评汰,因為這個類已經被很多其他類引用著纷捞,直接修改的話非常容易引起一些意想不到的問題。這時候就應該使用擴展方式來實現(xiàn)我們想要的新功能被去,比如可以新增方法主儡,或者使用繼承,這樣擴展惨缆,既可以保證我們對新需求的實現(xiàn)糜值,又可以避免直接修改方法帶來的一系列問題。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末踪央,一起剝皮案震驚了整個濱河市臀玄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌畅蹂,老刑警劉巖健无,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異液斜,居然都是意外死亡累贤,警方通過查閱死者的電腦和手機叠穆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來臼膏,“玉大人硼被,你說我怎么就攤上這事∩酰” “怎么了嚷硫?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長始鱼。 經常有香客問我仔掸,道長,這世上最難降的妖魔是什么医清? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任起暮,我火速辦了婚禮,結果婚禮上会烙,老公的妹妹穿的比我還像新娘负懦。我一直安慰自己,他們只是感情好柏腻,可當我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布纸厉。 她就那樣靜靜地躺著,像睡著了一般葫盼。 火紅的嫁衣襯著肌膚如雪残腌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天贫导,我揣著相機與錄音抛猫,去河邊找鬼。 笑死孩灯,一個胖子當著我的面吹牛闺金,可吹牛的內容都是我干的。 我是一名探鬼主播峰档,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼败匹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了讥巡?” 一聲冷哼從身側響起掀亩,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎欢顷,沒想到半個月后槽棍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年炼七,在試婚紗的時候發(fā)現(xiàn)自己被綠了缆巧。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡豌拙,死狀恐怖陕悬,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情按傅,我是刑警寧澤捉超,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站唯绍,受9級特大地震影響狂秦,放射性物質發(fā)生泄漏。R本人自食惡果不足惜推捐,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望侧啼。 院中可真熱鬧牛柒,春花似錦、人聲如沸痊乾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哪审。三九已至蛾魄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間湿滓,已是汗流浹背滴须。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留叽奥,地道東北人扔水。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像朝氓,于是被迫代替她去往敵國和親魔市。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內容