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