设计模式之禅【六大设计原则】
生活随笔
收集整理的這篇文章主要介紹了
设计模式之禅【六大设计原则】
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
大旗不揮,誰敢沖鋒--6大設(shè)計(jì)原則
-
單一職責(zé)原則
- “你設(shè)計(jì)的類符合SRP原則嗎?”--保準(zhǔn)對方立馬“萎縮”掉,而且還一臉崇拜的看著你,心想“老大確實(shí)英明!”。你可能會(huì)問了SRP是什么,別著急,往下看:
-
之前常用的模型--RBAC(Role-Based Access Control)基于角色的訪問控制,通過分配和取消角色來完成用戶權(quán)限的授予和取消,使動(dòng)作主體與資源的行為分離
- 類圖
- 這個(gè)類圖設(shè)計(jì)的有問題,用戶的屬性和用戶行為沒有分開,這是一個(gè)嚴(yán)重的錯(cuò)誤!應(yīng)該把用戶的信息抽取成一個(gè)BO(Business Object,業(yè)務(wù)對象),把行為抽取成一個(gè)Biz(Business Logic,業(yè)務(wù)邏輯),修正如下:
- 依賴單一原則
- 類圖
- 類圖
- 類圖
-
Single Responsibility Principle(SRP):Three should never be more than one reason for a class to change.
-
例:電話
電話通話時(shí)有四個(gè)過程:1. 撥號2. 通話3. 回應(yīng)4. 掛機(jī) -
?
這個(gè)接口有沒有問題,IPhone這個(gè)接口可不是只有一個(gè)職責(zé),它包含了兩個(gè)職責(zé):一個(gè)是協(xié)議管理,一個(gè)是數(shù)據(jù)傳送。dial()和hangup()兩個(gè)方法實(shí)現(xiàn)的是協(xié)議管理,分別負(fù)責(zé)撥號接通和掛機(jī)。chat()實(shí)現(xiàn)的是數(shù)據(jù)傳送。 - 拆分后的類圖:
- 繼續(xù)優(yōu)化:
- 單一職責(zé)原則的好處: 1. 類的復(fù)雜度降低,實(shí)現(xiàn)什么職責(zé)都有清晰明確的定義 2. 可讀性提高,復(fù)雜性降低->可讀性提高 3. 可維護(hù)性提高,可讀性提高->更易維護(hù) 4. 變更引起的風(fēng)險(xiǎn)降低
- 單一職責(zé)也適用于方法--一個(gè)方法盡可能的做一件事情
- 最佳實(shí)踐
- The is sometimes hard to see.單一職責(zé)非常優(yōu)秀,但是確實(shí)受非常多的因素制約,必須去考慮項(xiàng)目工期、成本、人員技術(shù)水平、硬件條件、網(wǎng)絡(luò)情況,甚至是政策,壟斷協(xié)議等因素。
- 對于單一職責(zé)原則,建議是接口一定做到單一職責(zé),類的設(shè)計(jì)盡量做到只有一個(gè)原因引起變化。
- 小結(jié) Single Responsibility Principle (SRP)從職責(zé)(改變理由)的側(cè)面上 為我們對類(接口)的抽象的顆粒度建立了判斷基準(zhǔn): 在為系統(tǒng)設(shè)計(jì)類(接口)的時(shí)候應(yīng)該保證它們的單一職責(zé)性。
-
里式替換原則
- 愛恨糾葛的父子關(guān)系
- 繼承優(yōu)點(diǎn)
- 代碼共享,減少創(chuàng)建類的工作量,每個(gè)子類都擁有父類的方法和屬性
- 提高代碼的重用性
- 龍生龍,鳳生鳳,老鼠生來會(huì)打洞--種
- 世界上沒有兩片完全相同的樹葉--不同
- 提高代碼的可擴(kuò)展性
- 提高代碼或項(xiàng)目的開放性
- 繼承缺點(diǎn)
- 繼承是入侵性的--只要繼承,就必須擁有父類的可繼承的屬性和方法
- 降低代碼的靈活性
- 增強(qiáng)了耦合性
- 繼承優(yōu)點(diǎn)
- 里式替換原則,讓繼承的“利”發(fā)揮最大作用,同時(shí)減少“弊”帶來的麻煩。
- if for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.
- “如果對每一個(gè)類型為S的對象o1,都有類型為T的對象o2,使得以T定義的所有程序P在所有對象o1都替換成o2時(shí),程序P的行為沒有發(fā)生變化,那么類型S是T的子類型”
- “只要父類能出現(xiàn)的地方子類就可以出現(xiàn),而且替換為子類也不會(huì)產(chǎn)生任何錯(cuò)誤或異常,但是反過來就不行了,有子類出現(xiàn)的地方,父類未必就能適應(yīng)”
- 里式替換原則為繼承定義了一個(gè)規(guī)范
- 子類必須完全實(shí)現(xiàn)父類的方法
- 子類可以有自己的個(gè)性
- 覆蓋或?qū)崿F(xiàn)父類的方法時(shí)輸入的參數(shù)可以被放大(eg:HashMap->Map)
- 最佳實(shí)踐 1. 盡量避免子類的“個(gè)性”,一旦子類有“個(gè)性”,這個(gè)子類和父類之間的關(guān)系就很難調(diào)和了 2. 把子類當(dāng)成父類使用,子類的“個(gè)性”就被抹殺了 3. 把子類單獨(dú)作為一個(gè)業(yè)務(wù)來使用,則會(huì)讓代碼間的耦合關(guān)系變得撲朔迷離
-
小結(jié)
里氏替換原則(Liskov Substitution Principle LSP)面向?qū)ο笤O(shè)計(jì)的基本原則之一。 任何基類可以出現(xiàn)的地方,子類一定可以出現(xiàn)。 LSP是繼承復(fù)用的基石,只有當(dāng)衍生類可以替換掉基類,軟件單位的功能不受到影響時(shí), 基類才能真正被復(fù)用,而衍生類也能夠在基類的基礎(chǔ)上增加新的行為?
里氏代換原則是實(shí)現(xiàn)開閉原則的重要方式之一,由于使用基類對象的地方都可以使用子類對象,因此在程序中盡量使用基類類型來對對象進(jìn)行定義,而在運(yùn)行時(shí)再確定其子類類型,用子類對象來替換父類對象。?
在使用里氏代換原則時(shí)需要注意如下幾個(gè)問題:(1)子類的所有方法必須在父類中聲明,或子類必須實(shí)現(xiàn)父類中聲明的所有方法。 根據(jù)里氏代換原則,為了保證系統(tǒng)的擴(kuò)展性,在程序中通常使用父類來進(jìn)行定義, 如果一個(gè)方法只存在子類中,在父類中不提供相應(yīng)的聲明,則無法在以父類定義的對象中使用該方法。(2) 我們在運(yùn)用里氏代換原則時(shí),盡量把父類設(shè)計(jì)為抽象類或者接口,讓子類繼承父類或?qū)崿F(xiàn)父接口, 并實(shí)現(xiàn)在父類中聲明的方法,運(yùn)行時(shí),子類實(shí)例替換父類實(shí)例,我們可以很方便地?cái)U(kuò)展系統(tǒng)的功能, 同時(shí)無須修改原有子類的代碼,增加新的功能可以通過增加一個(gè)新的子類來實(shí)現(xiàn)。 里氏代換原則是開閉原則的具體實(shí)現(xiàn)手段之一。(3) Java語言中,在編譯階段,Java編譯器會(huì)檢查一個(gè)程序是否符合里氏代換原則, 這是一個(gè)與實(shí)現(xiàn)無關(guān)的、純語法意義上的檢查,但Java編譯器的檢查是有局限的。
- 里式替換原則為繼承定義了一個(gè)規(guī)范
- 愛恨糾葛的父子關(guān)系
-
依賴倒置原則
- Dependence Inversion Principe(DIP)
- High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions. 1. 高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴抽象
2. 抽象不應(yīng)該依賴細(xì)節(jié)
3. 細(xì)節(jié)應(yīng)該依賴抽象
?
在java中的表現(xiàn)1. 模塊間的依賴通過抽象發(fā)生,實(shí)現(xiàn)類之間不發(fā)生直接的依賴關(guān)系,其依賴關(guān)系通過接口或抽象類產(chǎn)生的。2. 接口或抽象類不依賴實(shí)現(xiàn)類3. 實(shí)現(xiàn)類依賴接口或抽象類 -
言而無信,你需要太多契約
--論題:依賴倒置原則可以減少類間的耦合性,提高系統(tǒng)的穩(wěn)定性,降低并行開發(fā)引發(fā)的風(fēng)險(xiǎn),提高代碼的可讀性和可維護(hù)性 --反論題:不使用依賴倒置也可以減少類間的耦合性,提高系統(tǒng)的穩(wěn)定性,降低并行開發(fā)引起的風(fēng)險(xiǎn),提高代碼的可讀性和可維護(hù)性 --論據(jù):汽車?
舊版:public class Dvriver{public void driver(Benz benz){benz.run();}}public class Benz{public void run(){System.out.println("奔馳車在跑!");}}public class Client{public static void main(String[] args){Driver d=new Driver();d.driver(new Benz());}}//要想開寶馬車,還得把寶馬車創(chuàng)建出來public class BMW{public void run(){System.out.println("寶馬車在跑!");}}?
改進(jìn)版://建立兩個(gè)接口:IDriver和ICarpublic interface IDriver{public void driver(ICar car);}public interface ICar{public void run();} -
依賴的三種寫法
-
1.構(gòu)造函數(shù)傳遞依賴對象
public interface IDriver{public void driver(); }public class Driver implements IDriver{private ICar car;public Driver(ICar car){this.car=car;} } -
2.Setter方式傳遞依賴對象
public interface IDriver{public void setCar(ICar car);public void driver(); }public class Driver implements IDriver{private ICar car;public void setCar(ICar car){this.car=car;}public void driver(){this.car.run();} } - 3.接口聲明依賴對象
- 在接口的方法中聲明依賴對象 public interface IDriver{public void setCar(ICar car); }
-
- 最佳實(shí)踐 依賴倒置的本質(zhì)就是通過抽象(接口或抽象類)使各個(gè)類或模塊的實(shí)現(xiàn)彼此獨(dú)立,互不影響,實(shí)現(xiàn)模塊之間的松耦合,使用這個(gè)規(guī)則,遵循以下幾個(gè)規(guī)則:1. 每個(gè)類盡量都有接口或抽象類,或者抽象類和接口兩者都具備。2. 變量的表名類型盡量是接口或抽象類3. 任何類都不應(yīng)該從具體類派生4. 盡量不要覆寫基類的方法:基類如果是抽象類,而且方法已經(jīng)實(shí)現(xiàn),子類盡量不要覆寫5. 結(jié)合里式替換原則
- 到底什么是“依賴倒置”? 依賴正置:類間的依賴是實(shí)實(shí)在在的類間依賴,也是面向?qū)崿F(xiàn)編程。 依賴倒置:對現(xiàn)實(shí)世界進(jìn)行抽象,抽象的結(jié)果就是有了抽象類和接口,然后我們根據(jù)系統(tǒng)的需要就產(chǎn)生了抽象間的依賴,“倒置”就從這里產(chǎn)生。
-
接口隔離原則
- 接口分類(有點(diǎn)小小顛覆)
- 實(shí)例接口--Person zhangsan=new Person();中Person就是zhangsan的接口。
- 類接口--java中用interface關(guān)鍵字定義的接口
- 隔離定義
- Clients Should not be forced to depend upon interfaces that ther don't use.(客戶端不應(yīng)該依賴它不需要的接口)
- The dependency of one class to another one should depend on the smallest possible interface.(類間的依賴關(guān)系應(yīng)該建立在最小的接口上)
-
美女何其多,觀點(diǎn)各不同
選美女:1. 籠統(tǒng)(一個(gè)類中)1. 名字2. 臉蛋3. 氣質(zhì)4. 身材2. 隔離原則(拆分為兩個(gè)接口)1. 外形美2. 氣質(zhì)棒?
例:interface I { public void method1(); public void method2(); public void method3(); } class I1 implements I{public void method1(){System.out.print("我只要方法1");} public void method2(){} public void method3(){} } class I2 implements I{public void method1(){} public void method2(){System.out.print("我只要方法2");} public void method3(){} }class I3 implements I{public void method1(){} public void method2(){}public void method3(){System.out.print("我只要方法3");} }上面的例子方法的實(shí)現(xiàn)中,只想用改用的方法,這里就需要接口隔離了:interface IM1 { public void method1(); } interface IM2 { public void method2(); } interface IM3 { public void method3(); } class I1 implements IM1{public void method1(){System.out.print("我只要方法1");} } class I2 implements IM2{public void method2(){System.out.print("我只要方法2");} }class I3 implements IM3{public void method3(){System.out.print("我只要方法3");} } -
保證接口的純潔性
1. 接口一定要小不能違反單一職責(zé)原則 2. 接口要高內(nèi)聚提高接口、類、模塊的處理能力,減少對外的交互 3. 定制服務(wù)單獨(dú)為一個(gè)個(gè)體提供提供優(yōu)良的服務(wù),只提供訪問者需要的方法,減少可能引起的風(fēng)險(xiǎn) 4. 接口設(shè)計(jì)是有限度的接口設(shè)計(jì)粒度越小,系統(tǒng)越靈活,但要掌握好“度” - 最佳實(shí)踐 1. 一個(gè)接口只服務(wù)于一個(gè)子模塊和業(yè)務(wù)邏輯 2. 壓縮接口中的public方法,做到“滿身筋骨肉” 3. 已經(jīng)被污染了的接口,盡量去修改,若更改的風(fēng)險(xiǎn)大,則采用適配器模式進(jìn)行轉(zhuǎn)化處理 4. 了解環(huán)境,拒絕盲從
- 接口分類(有點(diǎn)小小顛覆)
-
迪米特法則
- Law of Demeter(Lod);Only talk to your immediate friends(只和朋友交流)
- 也稱最少知識原則(Least Knowledge Principe,LKP)
- 通俗的講:一個(gè)類應(yīng)該對自已需要耦合或調(diào)用的類知道的最少,你的內(nèi)部是如何復(fù)雜都和我沒關(guān)系,那是你自己的。
- 我的知識你知道的越少越好
- 四層含義 1. 只和直接朋友交流--不間接產(chǎn)生關(guān)系 2. 朋友間也是有距離的--兩只刺猬取暖,即可以相互取暖,又可以不傷害對方;兩個(gè)關(guān)系太親密,暴露的細(xì)節(jié)就多了,耦合關(guān)系變得異常牢固--即一個(gè)類的public屬性或方法越多,修改涉及的范圍就越大,因此,應(yīng)該盡量減少public的屬性和方法或者修改相應(yīng)的權(quán)限和修飾符 3. 是自己的就是自己的:如果一個(gè)方法放在本類中,既不增加類間關(guān)系,也對本類不產(chǎn)生負(fù)面影響,那就放置在本類中。 4. 謹(jǐn)慎使用Serializable:當(dāng)屬性的權(quán)限修改(假設(shè)擴(kuò)大權(quán)限)后,服務(wù)器上沒有做出相應(yīng)的反變更,就會(huì)報(bào)序列化失敗
-
例子
//將軍類 class JiangJun {//命令下屬統(tǒng)計(jì)兵數(shù)public void commond(XiaShu xiashu){List<ShiBing> lists = new ArrayList() ;for(int i=0;i<20;i++){lists.add(new ShiBing());}xiashu.counts(lists);} }//下屬類 class XiaShu{public int counts(List<ShiBing> lists){return lists.size();} }//士兵類 class ShiBing{... }//測試 public class Test{public static void main(String[] args){JiangJun j=new JiaJun();j.commond(new XiaShu());} }*****上述的案例,將軍類除了和下屬直接聯(lián)系,還添加了士兵,類間的耦合度驟然提高了,解決如下: //將軍類 class JiangJun {//命令下屬統(tǒng)計(jì)兵數(shù)public void commond(XiaShu xiashu){xiashu.counts();} }//下屬類 class XiaShu{public int counts(){List<ShiBing> lists = new ArrayList() ;for(int i=0;i<20;i++){lists.add(new ShiBing());}return lists.size();} }//士兵類 class ShiBing{... }//測試 public class Test{public static void main(String[] args){JiangJun j=new JiaJun();j.commond(new XiaShu());} } -
最佳實(shí)踐
1. 迪米特法則的核心觀念就是類間解耦,弱耦合,只有弱耦合了,類的復(fù)用率才可以提高,但是要求的結(jié)果就是產(chǎn)生了大量的中轉(zhuǎn)或跳轉(zhuǎn)類,導(dǎo)致系統(tǒng)的復(fù)雜度提高,同時(shí)也給維護(hù)帶來了難度。需要反復(fù)權(quán)衡,既做到結(jié)構(gòu)清晰,又做到高內(nèi)聚,低耦合 2. 類間跳轉(zhuǎn)不超過兩次是可以接受的
-
開閉原則
- 建立一個(gè)穩(wěn)定的、靈活的系統(tǒng)
- Software entities like classes,modules and functions should be open for extension but closed for modifications.(一個(gè)軟件實(shí)體如類、模塊和函數(shù)應(yīng)該對擴(kuò)展開放,對修改關(guān)閉)
- 軟件實(shí)體 1. 項(xiàng)目或軟件產(chǎn)品中按照一定邏輯規(guī)劃劃分的模塊 2. 抽象和類 3. 方法
- 一般解決辦法
- 修改接口
- 修改實(shí)現(xiàn)類
- 通過擴(kuò)展實(shí)現(xiàn)變化
- 變化的類型
- 邏輯變化
- 子模塊變化
- 可見視圖變化
-
例子
//畫動(dòng)物類 class PaintAnimal{public paint(String name){if("cat".equals(name)){PCat pcat=new PCat();pcat.paint();}else if("dog".equals(name)){PDog pdog=new PDog();pdog.paint();}} }//畫貓類 class PCat{public void paint(){System.out.print("^~^ 喵");} }//畫狗類 class PDog{public void paint(){System.out.print(":~: 汪汪");} }//測試類 public class Test{public static void main (String[] args){PaintAnimal pa=new PaintAnimal();pa.paint("cat");//畫貓} }*****以上代碼中,根據(jù)String的傳入來進(jìn)行判斷,首先這是很耗費(fèi)時(shí)間的,需要一個(gè)一個(gè)判斷, 而且如果要新增畫蛇,畫馬...這樣改動(dòng)代碼就稍稍麻煩了,修改了源代碼,而不是擴(kuò)展,改進(jìn)如下: //畫動(dòng)物類 class PaintAnimal{public paint(PAnimal pa){pa.paint();} }//新增抽象畫動(dòng)物類 abstract class PAnimal{public void paint(); }//畫貓類 class PCat extends PAnimal{public void paint(){System.out.print("^~^ 喵");} }//畫狗類 class PDog extends PAnimal{public void paint(){System.out.print(":~: 汪汪");} }//測試類 public class Test{public static void main (String[] args){PaintAnimal pa=new PaintAnimal();pa.paint(new PCat());//畫貓} } -
開閉原則的重要性
1. 開閉原則對測試的影響--變化產(chǎn)生時(shí),原有的健壯代碼是否可以不修改,只通過擴(kuò)展來進(jìn)行修改(單元測試中,Keep the bar green to keep the code clean,即保持綠條有助于代碼整潔),新增加的類,新增加的測試方法,只要是正確的就行了 2. 開閉原則可以提高代碼的復(fù)用性 3. 開閉原則可以提高代碼的可維護(hù)性 4. 面向?qū)ο蟮拈_發(fā)要求 - 如何使用開閉原則 1. 抽象約束1. 通過接口或抽象類約束擴(kuò)展,對擴(kuò)展進(jìn)行邊界限定,不允許出現(xiàn)在接口或抽象類中不存在的public的方法2. 參數(shù)類型,引用對象盡量使用接口或抽象類,而不是實(shí)現(xiàn)類3. 抽象層盡量保持穩(wěn)定,一旦確定即不允許修改 2. 元數(shù)據(jù)(metadata)控制模塊行為 3. 制定項(xiàng)目章程 4. 封裝變化1. 將相同的變化封裝到一個(gè)接口或抽象類中2. 將不同的變化封裝到不同的接口或抽象類中,不應(yīng)該有兩個(gè)不同的變化出現(xiàn)在同一個(gè)接口或抽象類中
- 最佳實(shí)踐
- 開閉原則也只是一個(gè)原則
- 項(xiàng)目章程非常重要
- 預(yù)知變化
總結(jié)
- Single Responsibility Principle單一職責(zé)原則
- Open Closed Principe開閉原則
- Liskov Substitution Principe里式替換原則
- Law of Demeter迪米特法則
- Interface Segregation Principe接口隔離原則
- Dependence Inversion Principe依賴倒置原則
- 把上面的六個(gè)原則的英文的首字母拿出來拼一下,就是SOLID(solid,穩(wěn)定的),其代表的含義也就是把這六個(gè)結(jié)合使用的好處:建立穩(wěn)定、靈活、健壯的設(shè)計(jì),而開閉原則又是重中之重、最基礎(chǔ)的原則,是其他五大原則的精神領(lǐng)袖。
說明
- 摘自秦小波《設(shè)計(jì)模式之禪》第2版;
- 僅供學(xué)習(xí),嚴(yán)禁商業(yè)用途;
總結(jié)
以上是生活随笔為你收集整理的设计模式之禅【六大设计原则】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面对人工智能,我们应有的态度
- 下一篇: 设计模式(八)装饰模式