初探設計模式六大原則

前言

我想用貼近生活的語句描述一下本身對六種原則的理解。也就是不作專業性的闡述,而是描述一種本身學習後的理解和感覺,由於能力通常並且水平有限,也許舉的例子不盡穩當,還請諒解
 
本來我是想用JavaScript編寫的,可是JavaScript到如今尚未提出接口的概念,而用TypeScript寫又感受普及度還不算特別高,因此仍是決定用Java語言編寫

目錄

設計模式有六大原則
  • 單一職責原則linux

  • 里氏替換原則程序員

  • 依賴倒置原則數據庫

  • 接口隔離原則編程

  • 迪米特原則ubuntu

  • 開閉原則windows

首先要提的是:六大原則的靈魂是面向接口,以及如何合理地運用接口

P1.單一職責原則(Single Responsibility Principle)

應該有且僅有一個緣由引發類的變動(There should never be more than one reason for a class to change)。
 
爲了達到這個目標,咱們須要對類和業務邏輯進行拆分。劃分到合適的粒度,讓這些各自執行單一職責的類,各司其職。 讓每一個類儘可能行使單一的功能,實現「高內聚」,這個結果也使得類和類之間不會有過多冗餘的聯繫,從而「低耦合」。
 
好比咱們如今有了這樣一個類
public class People {
    public void playCnBlogs () {
        System.out.println("刷博客");
    }
    public void doSports () {
        System.out.println("打乒乓球");
    }
    public void work () {
        System.out.println("工做");
    }
}

 

如今看起來有點混亂,由於這個類裏面混合了三個職責:
  • 刷博客園,這是博主的職責設計模式

  • 打乒乓球,這是業餘運動愛好者的職責網絡

  • 工做,這是「普普統統上班族」的職責(彷佛暴露了什麼)架構

OK,正如你所見,既然咱們要遵循單一職責,那麼怎麼作呢?固然是要拆分了
 
咱們要根據接口去拆,拆分紅三個接口去約束People類(不是把People類拆了哈)
// 知乎er
public interface Blogger {
    public void playCnBlogs();
}
// 上班族
public interface OfficeWorkers {
    public void work();
}
// 業餘運動愛好者
public interface AmateurPlayer {
    public void doSports();
}

 

而後在People中繼承這幾個接口
public class People implements Blogger,AmateurPlayer,OfficeWorkers{
    public void playCnBlogs () {
        System.out.println("刷博客園");
    }
    public void doSports () {
        System.out.println("打乒乓球");
    }
    public void work () {
        System.out.println("工做");
    }
}

 

最後建立實例運行一下
public class Index {
    public static  void main (String args []) {
        People people = new People();
        Blogger blogger = new People();
        blogger.playCnBlogs(); // 輸出:刷博客園
        OfficeWorkers workers = new People();
        workers.work(); // 輸出: 工做
        AmateurPlayer players = new People();
        players.doSports(); // 輸出:打乒乓球
    }
}

  

備註:這個原則不是死的,而是活的,在實際開發中固然還要和業務相結合,不會純粹爲了理論貫徹單一職責,就像數據庫開發時候,不會徹底遵循「三大範式」,而是容許必定冗餘的

P2.里氏替換原則(liskov substitution principle)

里氏替換原則,一種比較好的理解方式是: 全部引用基類的地方必須能透明地使用其子類的對象。 換句話說,子類必須徹底實現父類的功能。凡是父類出現的地方,就算徹底替換成子類也不會有什麼問題。
 
以上描述來自《設計模式之禪》,剛開始看的時候我有些疑惑,由於一開始以爲:只要繼承了父類不均可以調用父類的方法嗎?爲何還會有里氏替換所要求的:子類必須徹底實現父類的功能呢, 難不成繼承的子類還能夠主動「消除」父類的方法?
 
還真能夠,請看
父類
public abstract class Father {
    // 認真工做
    public abstract void work();
    // 其餘方法
}

ide

public class Son extends Father {
    @Override
    public void work() {
     // 我實現了爸爸的work方法,旦我什麼也不作!
    }
}

 

子類雖然表面上實現了父類的方法,可是他實際上並無實現父類要求的邏輯。里氏替換原則要求咱們避免這種「塑料父子情」,若是出現子類不得不脫離父類方法範圍的狀況, 採起其餘方式處理,詳情參考《設計模式之禪》

(其實我的以爲《禪》的做者其實講的「父類」其實着重指的是抽象類)

P3.依賴倒置原則 (dependence inversion principle)

不少文章闡述依賴倒置原則都會闡述爲三個方面
  • 高層的模塊不該該依賴於低層的模塊,這二者都應該依賴於其抽象

  • 抽象不該該依賴細節

  • 細節應該依賴抽象

換句話說, 高層次的類不該該依賴於,或耦合於低層次的類,相反,這二者都應該經過相關的接口去實現。要面向接口編程,而不是面向實現編程,因此編程的時候並非按照符合咱們邏輯思考的「依賴關係」去編程掉的,這種不符,就是依賴倒置
 
舉個例子, 類比如是道德,接口比如是法律。
 
道德呢,有上層的也有下層的,春秋時代,孔聖人提出了上層道德理論:「仁」的思想,並進一步細化爲低層道德理論:「三綱五常」(高層模塊和底層模塊),想要以此規約衆生,實現天下大同。但是奈何民衆的道德終究仍是靠不住(沒有接口約束的類,可能被混亂修改),況且道德標準是會隨物質經濟的變化而變化的,孔子時代和咱們今天的已經大有不一樣了。(類可能會發生變化) 因此才須要法律來進一步框定和要求道德。(咱們用接口來約束和維護「類」,就比如用法律來維護和規約道德同樣。)假如將來道德倫理的標杆發生了變化,確定是先修繕法律,而後再次反向規制和落實道德(面向接口編程,而不是面向實現編程)。
 
咱們看下下面沒有遵循依賴倒置原則的代碼是怎樣的,咱們設計了兩個類:Coder類和Linux類,而且讓它們之間產生交互:Coder對象的develop方法接收Linux對象而且輸出系統名
// 底層模塊1:開發者
public class Coder {
    public void develop (Linux linux) {
        System.out.printf("開發者正在%s系統上進行開發%n",linux.getSystemName());
    }
}
// 底層模塊2:Linux操做系統
public class Linux {
    public String name;
    public Linux(String name){
        this.name = name;
    }
    public String getSystemName () {
        return this.name;
    }
}
// 高層模塊
public class Index {
    public static  void main (String args []) {
        Coder coder = new Coder();
        Linux ubuntu = new Linux("ubuntu系統"); // ubuntu是一種linux操做系統
        coder.develop(ubuntu);
    }
}

 

輸出
開發者正在ubuntu系統系統上進行開發 

可是咱們能發現其中的問題:

操做系統不只僅有Linux家族,還有Windows家族,若是咱們如今須要讓開發者在windows系統上寫代碼怎麼辦呢? 咱們可能要新建一個Windows類,可是問題來了,Code.develop方法的入參數類型是Linux,這樣以來改造就變得很麻煩。
 
讓咱們利用依賴倒置原則改造一下,咱們定義OperatingSystem接口,將windows/Linux抽象成操做系統,這樣,OperatingSystem類型的入參就能夠接收Windows或者Linux類型的參數了
// 程序員接口
public interface Programmer {
    public void develop (OperatingSystem OS);
}
// 操做系統接口
public interface OperatingSystem {
    public String getSystemName ();
}
// 低層模塊:Linux操做系統
public class Linux implements  OperatingSystem{
    public String name;
    public Linux (String name) {
        this.name = name;
    }
    @Override
    public String getSystemName() {
        return this.name;
    }
}
// 低層模塊:Window操做系統
public class Window implements OperatingSystem {
    String name;
    public Window (String name) {
        this.name = name;
    }
    @Override
    public String getSystemName() {
        return this.name;
    }
}
// 低層模塊:開發者
public class Coder implements Programmer{
    @Override
    public void develop(OperatingSystem OS) {
        System.out.printf("開發者正在%s系統上進行開發%n",OS.getSystemName());
    }
}
// 高層模塊:測試用
public class Index {
    public static  void main (String args []) {
        Programmer coder = new Coder();
        OperatingSystem ubuntu = new Linux("ubuntu系統"); // ubuntu是一種linux操做系統
        OperatingSystem windows10 = new Window("windows10系統"); // windows10
        coder.develop(ubuntu);
        coder.develop(windows10);
    }
}

 

雖然接口的加入讓代碼多了一些,可是如今擴展性變得良好多了,即便有新的操做系統加入進來,Coder.develop也能處理

P4. 接口隔離原則(interface segregation principle)

接口隔離原則的要求是:類間的依賴關係應該創建在最小的接口上。 這個原則又具體分爲兩點
  1. 接口要足夠細化,固然了,這會讓接口的數量變多,可是每一個接口會具備更加明確的功能

  2. 在1的前提下,類應該依賴於「最小」的接口上

 
舉個例子, 中秋節其實只過了一個多月,如今假設你有一大盒「五仁月餅」想帶回家餵豬,可是無奈的是包包過小放不下,並且一盒沉重的月餅對瘦弱的你是個沉重的負擔。這個時候,咱們能夠把月餅盒子拆開,選出一部分本身須要(wei zhu)的月餅,放進包包裏就好啦,既輕便又靈活。
 
仍是上代碼吧,好比咱們有這樣一個Blogger的接口,裏面涵蓋了一些可能的行爲。大多數博客用戶會保持友善,同時根據本身的專業知識認真寫文章。但也有少數的人會把生活中的負面能量帶到網絡中
public interface Blogger {
    // 認真撰文
    public void seriouslyWrite();
    // 友好評論
    public void friendlyComment();
    // 無腦擡槓
    public void argue();
    // 鍵盤攻擊
    public void keyboardAttack ();
}

 

咱們發現,這個接口能夠進一步拆分紅兩個接口,分別命名爲PositiveBlogger,NegativeBlogger。這樣,咱們就把接口細化到了一個合理的範圍
public interface PositiveBlogger {
    // 認真撰文
    public void seriouslyWrite();
    // 友好評論
    public void friendlyComment();
}

public interface NegativeBlogger {
    // 無腦擡槓
    public void argue();
    // 鍵盤攻擊
    public void keyboardAttack ();
}
 
>> 備註:妥善處理 單一職責原則 和 接口隔離原則的關係
事實上,有兩點要說明一下
  1. 單一職責原則和接口隔離原則雖然看起來有點像,好像都是拆分,可是其實側重點是不同的,「職責」的粒度實際上是比「隔離接口」的粒度要大的

  2. 基於1中闡述的緣由,其實 單一職責原則 和 接口隔離原則是可能會產生衝突的,由於接口隔離原則要求粒度儘量要細,可是單一職責原則卻不一樣,它要求拆分既不能過粗,但也不能過細,若是把本來單一職責的接口分紅了「兩個0.5職責的接口」,那麼這就是單一職責所不能容許的了。

  3. 當二者衝突時,優先遵循 單一職責原則

P5.迪米特原則 (law of demeter)

迪米特原則又叫最少知道原則,在實現功能的前提下,一個對象接觸的其餘對象應該儘量少,也即類和類之間的耦合度要低。
 
舉個例子, 咱們常常說要「減小無效社交」,不要老是一昧的以交朋友的數量衡量本身的交際能力,不然會讓本身很累的,也會難以打理好複雜的人際關係。對於並不很外向的人,多數時候和本身有交集的朋友交往就能夠了。
 
咱們看下代碼:
有以下場景,如今你和你的朋友想要玩一個活動,也許是鬥地主等遊戲,這個時候須要再喊一我的,因而你讓你的朋友幫你再叫一我的,有代碼以下
// 個人直接朋友
public class MyFriend {
    // 找他的朋友
    public void findHisFriend (FriendOfMyFriend fof) {
      System.out.println("這是朋友的朋友:"+ fof.name);
    }
}

// 朋友的朋友,但不是個人朋友
public class FriendOfMyFriend {
    public String name;
    public FriendOfMyFriend(String name) {
      this.name = name;
    }
}

//
public class Me {
    public void findFriend (MyFriend myFriend) {
      System.out.println("我找我朋友");
      // 注意這段代碼
      FriendOfMyFriend fmf = new FriendOfMyFriend("陌生人");
      myFriend.findHisFriend(fmf);
    };
}

 

這時咱們發現一個問題,你和你朋友的朋友並不認識,可是他卻出如今了你的「找朋友」的動做當中(在findFriend方法內),這個時候,咱們認爲這違反了迪米特原則(最少知道原則),迪米特原則咱們對於對象關係的處理,要減小「無效社交」,具體原則是
  • 一個類只和朋友類交流,朋友類指的是出如今成員變量、方法的輸入輸出參數中的類

  • 一個類不和陌生類交流,即沒有出如今成員變量、方法的輸入輸出參數中的類

所謂的「不交流」,就是不要在代碼裏看到他們
 
咱們改造一下上面的代碼
// 我朋友
public class MyFriend {
    public void findHisFriend () {
        FriendOfMyFriend fmf = new FriendOfMyFriend("陌生人");
        System.out.println("這是朋友的朋友:"+ fmf.name);
    }
}
// 朋友的朋友,但不是個人朋友
public class FriendOfMyFriend {
    public String name;
    public FriendOfMyFriend(String name) {
        this.name = name;
    }
}

//
public class Me {
    public void findFriend (MyFriend myFriend) {
        System.out.println("我找我朋友");
        myFriend.findHisFriend();
    };
}

 

P6. 開閉原則(open closed principle)

開閉原則的意思是,軟件架構要:對修改封閉,對擴展開放
 
舉個例子
 
好比咱們如今在玩某一款喜歡的遊戲,A鍵攻擊,F鍵閃現。這個時候咱們想,若是遊戲能額外給我定製一款「K」鍵,殘血時解鎖從而一擊OK對手完成5殺,那豈不美哉,這就比如是「對擴展開放」。
 
可是呢,若是遊戲忽然搞個活動,把閃現/攻擊/技能釋放的鍵盤統統換個位置,給你一個「雙十一的驚喜」,這恐怕就給人帶來慘痛的回憶了。因此咱們但願已有的結構不要動,也不能動,要「對修改封閉」
(本人不玩遊戲,這些是本身查到的,若是錯誤還請指正)

總結

  1. 原則不是死板的而是靈活的

  2. 一些原則實際上是存在必定的衝突的,重要的是權衡,是掌握好度

  3. 六大原則是23種設計模式的靈魂,六大原則指導了設計模式,設計模式體現了六大原則

就像不少人說的,其實設計模式是一種思想,關鍵的仍是怎樣和業務結合起來,我也剛學習不久呢,若是前輩們有什麼好的看法,還請在評論區指點一下,不勝感激 

相關文章
相關標籤/搜索