設計模式就該這麼學:要走心才能遵循設計模式五大原則(第二篇) 設計模式就該這麼學:爲何要學設計模式?(開篇漫談) 設計模式就該這麼學:要走心才能遵循設計模式五大原則(第二篇) 設計模式就該這麼學:以

 

摘要:設計模式是基於必定問題解決規律產生,天然也會有些公用的東西,這個公用的東西就是我今天要講的設計模式的五大原則,固然原則只是戰略層面的指導,沒有代碼能徹底遵照着五大原則,要根據實際(zou)狀況(xin)合理取捨html

  本文做爲《設計模式就該這麼學》系列文章的第二篇,今天咱們來聊一下設計模式中的五大原則!java

  《設計模式就該這麼學》系列文章: react

設計模式就該這麼學:爲何要學設計模式?(開篇漫談)編程

設計模式就該這麼學:要走心才能遵循設計模式五大原則(第二篇)設計模式

設計模式就該這麼學:以微信訂閱號來說觀察者模式(第三篇)微信

觀察者模式實際應用:監聽線程,意外退出線程後自動重啓ide

1、究竟是哪五大原則

  設計模式(面向對象)五大原則能夠簡稱爲SOLID,SOLID是面向對象設計和編程(OOD&OOP)中幾個重要編碼原則(Programming Priciple)的首字母縮寫,它目的就是爲了寫出可複用、可擴展、高內聚、低耦合的代碼。post

固然原則只是戰略層面的指導,沒有代碼能徹底遵照着五大原則,要根據實際(zou)狀況(xin)合理取捨。學習

下面咱們來看看SOLID的具體描述:編碼

 

SRP The Single Responsibility Principle  單一責任原則
OCP The Open Closed Principle  開放封閉原則
LSP The Liskov Substitution Principle 里氏替換原則
DIP The Dependency Inversion Principle 依賴倒置原則
ISP The Interface Segregation Principle 接口分離原則

一、單一職責

  一個類只作一種類型責任,當這個類須要承當其餘類型的責任的時候,就須要分解這個類。不過在現實開發中,這個原則是最不可能遵照的,由於每一個人對一個類的哪些功能算是同一類型的職責判斷都不相同。

二、開放封閉原則

  軟件實體應該是可擴展,而不可修改的。也就是說,你寫完一個類,要想添加功能,不能修改原有類,而是想辦法擴展該類。即對擴展開放,對修改關閉。

三、里氏替換原則

  當一個子類的實例應該可以替換任何其超類的實例時,它們之間才具備is-A關係。也就是說接口或父類出現的地方,實現接口的類或子類能夠代入,這主要依賴於多態和繼承。

四、 接口分離原則

  (1)不能強迫用戶去依賴那些他們不使用的接口。換句話說,使用多個專門的接口比使用單一的總接口總要好。

  (2)不要提供一個大的接口包括全部功能,應該根據功能把這些接口分割,減小依賴。

五、依賴倒置原則

  (1)高層模塊不該該依賴於低層模塊,兩者都應該依賴於抽象

  (2)抽象不該該依賴於細節,細節應該依賴於抽象

2、舉個例子來介紹下五大原則

   以動物爬樹來說單一原則,用一個類描述這個場景

class Animal {
    public void breathe(string animal)
    {
        System.out.println(animal+"爬樹");
    }
}
class Program
{
    static void Main(string[] args)
    {
        Animal animal = new Animal();
        animal.breathe("貓");
        animal.breathe("蛇");
    }
}

咱們發現不是全部動物都會爬樹的,好比鳥就不會爬樹,根據單一職責原則,咱們將Animal類細分爲爬樹動物類和飛翔動物類,以下所示: 

   class SpeelAnimal
    {
        public void pashu(string animal)
        {
            System.out.println(animal+"爬樹");
        }
    }
    class FlyAnimal
    {
        public void fly(string animal)
        {
        	System.out.println(animal + "飛翔");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            SpeelAnimal speelAnimal = new SpeelAnimal();
            speelAnimal.pashu("蛇");
            FlyAnimal flyAnimal = new FlyAnimal();
            flyAnimal.breathe("麻雀");
        }
    }

 咱們發現這樣修改的花銷很大,既要將原來的類分解,又要修改客戶端。而直接修改Animal類雖然違背了單一職責原則,但花銷小的多,以下所示

class Animal
{
    public void action(string animal)
    {
        if ("麻雀".Equals(animal))
        {
            System.out.println(animal + "飛翔");
        }
        else {
            System.out.println(animal + "攀爬");
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        Animal animal = new Animal();
        animal.action("蛇");
        animal.action("麻雀");
    }
}

 能夠看到,這樣代碼量少了不少,可還有一個問題,雞會打鳴,而蛇不會,有一天須要增長雞打鳴的方法,會發現又要直接修改上面的方法,這種修改直接在代碼級別違背了單一職責原則,雖然修改起來最簡單,但隱患最大。還有一種修改方式:

 Animal
    {
        public void pashu(string animal)
        {
            System.out.println(animal+"爬樹");
        }

        public void fly(string animal)
        {
        	System.out.println(animal + "飛翔");
        }
        
        public call()
       {
                System.out.println(animal + "鳴叫");
        }
    
    }
    class Program
    {
        static void Main(string[] args)
        {
            Animal animal = new Animal();
            animal.pashu("蛇");        
            animal.call("麻雀");
        }
    }    

 這種修改方式沒有改動原來的方法,而是在類中新加了一個方法,這樣雖然違背了單一職責原則,但在方法級別上倒是符合單一職責原則的。那麼在實際編程中,採用哪種呢?個人原則是,只有邏輯足夠簡單,才能夠在代碼級違反單一職責原則;只有類中方法數量足夠少,才能夠在方法級別違反單一職責原則。 

 遵循單一職責的優勢:
1)下降類的複雜度,一個類只負責一項職責。
2)提升類的可讀性,可維護性
3)下降變動引發的風險。

開放封閉原則這個沒什麼好說的,再來講說里氏替換原則,它其實有兩種定義,

第一種定義:若是對每個類型爲S的對象o1,都有類型爲T的對象o2,使得以T定義的全部程序P在全部的對象o1都代換爲o2,程序P的行爲沒有發生變化,那麼類型S是類型T的子類型。

第二種定義:全部引用基類的地方必須透明的使用其子類的對象。第二種定義明確的說,只要父類能出現的地方子類也能夠出現,並且替換爲子類不會產生任何錯誤或異常,可是反過來就不行,有子類出現的地方,父類未必就能適應。由定義可知,在使用繼承時,會遵循里氏替換原則,集成的好處自沒必要多說,代碼共享,重用性高,可是在子類中儘可能不要重寫和重載父類的方法。爲何呢?

  • 繼承是侵入性的,只要繼承,就必須擁有父類的全部方法和屬性
  • 下降了代碼的靈活性,子類必須擁有父類的屬性和方法,讓子類有了一些約束
  • 增長了耦合性,當父類的常量,變量和方法被修改了,須要考慮子類的修改,這種修改可能帶來很是糟糕的結果,要重構大量的代碼。

   接下來以一個兩數相減的小李子說明一下:

class A{
    public int func1(int a,int b){
        return a-b;
    }
}
public class Client{
    public static void main(string[] args){
        A a=new A();
        System.out.println("100-50="+a.func1(100,50));
        System.out.println("100-80="+a.func1(100,80));
    }
}

 運行結果:

100-50=50
100-80=20

後來,咱們須要增長一個新的功能:完成兩數相加,而後再與100求和,由類B來負責。

Class B extends A{
    public int func1(int a,int b){
        return a+b;
    }
    public int func2(int a,int b){
        return func1(a,b)+100;
    }
}
public class Client{
    public static void main(string[] args){
        B a=new B();
        System.out.println("100-50="+b.func1(100,50));
        System.out.println("100-80="+b.func1(100,80));
        System.out.println("100+20+100="+b.func2(100,20));
    }
}

運行結果:

100-50=150
100-80=180
100+20+100=220

咱們發現原來運行正常的相減功能發生了錯誤,這就是由於重寫A類中的方法致使。

因此若是非要重寫父類的方法,通用的作法是:原來的父類和子類都繼承一個更通俗的基類,原有的繼承關係去掉,採用依賴,聚合,組合等關係代替

接下來講說依賴致使原則,它的定義是:

  • 高層模塊不該該依賴低層模塊,二者都應該依賴抽象
  • 抽象不該該依賴細節
  • 細節應該依賴抽象

依賴倒置原則能夠說是這裏面最難用的一個原則了,那爲何咱們還要要遵循依賴倒置原則?接下來以一個看視頻的例子說明下問題,張三喜歡看douyin小視頻

//Bibili類
public class Bibili類{
    //閱讀文學經典
    public void watch(){
       System.out.println("看douyin視頻");
    }
}

 再來看張三類

//張三類
public class Zhangsan{
    //閱讀文學經典
    public void watch(douyin bili){
        bili.watch();
    }
}

場景類

public class Client{
   public static void main(Strings[] args){
      Zhangsan zhangsan = new Zhangsan();
      douyin  bili = new douyin ();
      //zhangsan看douyin視頻
      zhangsan.watch(bili);
   }

}

張三看了一段時間的小視頻時候,又想看電視機劇《白夜追兇》,發現這個只在優酷才能看,因而實現一個優酷類

public class Youku{
    //看電視劇
    public void watch(){
       System.out.println("看白夜追兇");
    }
}

如今咱們再來看代碼,發現張三類的read方法只與Bilibi類是強依賴,緊耦合關係,張三居然閱讀不了小說類。這與現實明顯的是不符合的,代碼設計的是有問題的。那麼問題在那裏呢?
咱們看張三類,此類是一個高層模塊,而且是一個細節實現類,此類依賴的是一個Bilibi類,而Bilibi類也是一個細節實現類。這是否是就與咱們說的依賴倒置原則相違背呢?依賴倒置原則是說咱們的高層模塊,實現類,細節類都應該是依賴與抽象,依賴與接口和抽象類。
爲了解決張三看電影的問題,咱們根據依賴倒置原則先抽象一個閱讀者接口,下面是完整的uml類圖:

觀看者接口

public interface Watcher{
   //閱讀
   public void watch(Watcher watch){
       watch.watch();
   }

}

視頻接口

public interface Video{
   //被觀看
   public void video();
}

再定義douyin類和Youku類

//douyin類
public class douyin implements Watcher{
    public void watcher(){
       System.out.println("看douyin小視頻");
    }
}
//Youku類
public class Youku implements Watcher{
    public void watcher(){
       System.out.println("看白夜追兇");
    }
}

而後實現張三類

//張三類
public class Zhasan implements Video{
    //觀看
    public void watch(Video video){
        video.watch();
    }
}

而後再讓張三來看douyin視頻和優酷電視劇《白夜追兇》

public class Client{
   public static void main(Strings[] args){
      Watcher zhansan = new Zhansan();
      Video douyin = new douyin();
      //張三看douyin小視頻
      zhangsan.watch(douyin);

      Video youku = new Youku();
      //張三看douyin小視頻
      zhangsan.watch(youku);
   }

}

至此,張三是能夠看douyin小視頻,又能夠看優酷電視劇《白夜追兇》了,目的達到了。

爲何依賴抽象的接口能夠適應變化的需求?這就要從接口的本質來講,接口就是把一些公司的方法和屬性聲明,而後具體的業務邏輯是能夠在實現接口的具體類中實現的。因此咱們當依賴對象是接口時,就能夠適應全部的實現此接口的具體類變化。由此也能夠看出依賴倒置原則用好能夠減小類間的耦合性,提升系統的穩定,下降並行開發引發的風險,提升代碼的可讀性和可維護性。

 

至此,設計模式的五大原則介紹完畢,接下來一篇文章我會以微信訂閱號來說觀察者模式,同時會講到實際應用的一個例子!

 

 學習本就是一個不斷模仿、練習、再到最後面本身原創的過程。

雖然可能歷來不能寫出超越網上通類型同主題博文,但爲何仍是要寫?
於本身而言,博文主要是本身總結。假設本身有觀衆,畢竟講是最好的學(見下圖)。

於讀者而言,筆者能在這個過程get到知識點,那就是共贏了。
固然因爲筆者能力有限,或許文中存在描述不正確,歡迎指正、補充!
感謝您的閱讀。若是本文對您有用,那麼請點贊鼓勵。

相關文章
相關標籤/搜索