設計模式之禪1

設計模式之禪1

大旗不揮,誰敢衝鋒--6大設計原則

  • 單一職責原則java

    • 「你設計的類符合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的子類型」
      • 「只要父類能出現的地方子類就能夠出現,並且替換爲子類也不會產生任何錯誤或異常,可是反過來就不行了,有子類出現的地方,父類未必就能適應」

     

    例:
        //父類--定義了eat()方法
        abstract class Super{
            public void eat();
        }
    
        //貓子類
        class CatSub extends Super{
            public void eat(){
                System.out.print("eat fish~~");
            }
        }
    
        //狗子類
        class DogSub extends Super{
            public void eat(){
                System.out.print("eat meat~~");
            }
        }
    
        //測試
        public class Test{
            public static void main (String[] args){
                Super s1=new CatSub();//CatSub s1=new Catsub();
                s1.eat();
    
                Super s2=new CatSub();//CatSub s2=new Catsub();
                s2.eat();
            }
        }
    • 裏式替換原則爲繼承定義了一個規範
      • 子類必須徹底實現父類的方法
      • 子類能夠有本身的個性
      • 覆蓋或實現父類的方法時輸入的參數能夠被放大(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和ICar
      
          public 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. 將不一樣的變化封裝到不一樣的接口或抽象類中,不該該有兩個不一樣的變化出如今同一個接口或抽象類中
    • 最佳實踐

       

      1. 開閉原則也只是一個原則
      2. 項目章程很是重要
      3. 預知變化

總結

  • Single Responsibility Principle單一職責原則
  • Open Closed Principe開閉原則
  • Liskov Substitution Principe裏式替換原則
  • Law of Demeter迪米特法則
  • Interface Segregation Principe接口隔離原則
  • Dependence Inversion Principe依賴倒置原則
  • 把上面的六個原則的英文的首字母拿出來拼一下,就是SOLID(solid,穩定的),其表明的含義也就是把這六個結合使用的好處:創建穩定、靈活、健壯的設計,而開閉原則又是重中之重、最基礎的原則,是其餘五大原則的精神領袖。

說明

  • 摘自秦小波《設計模式之禪》第2版;
  • 僅供學習,嚴禁商業用途;
  • 代碼手寫,若有錯誤,根據上下文改正;
相關文章
相關標籤/搜索