我的解讀設計模式

概述

設計原則就一本菜譜,告訴咱們一道美味的菜應該是什麼樣的,或者說須要具有什麼。可是又沒有一個固化或可測量的標準。寫代碼就和烹飪同樣,只有當本身品嚐之後才知其味。java

1 開閉原則

定義:

開閉原則(Open-Closed Principle, OCP):一個軟件實體應當對擴展開放,對修改關閉。即軟件實體應儘可能在不修改原有代碼的狀況下進行擴展。編程

解讀

開閉原則很簡單,就是當需求變動的時候,儘可能不要修改已有代碼。開閉原則是整個設計原則的核心思想。若是當你發現某個需求的改動須要涉及許多地方的代碼改動,那麼你的代碼頗有多是不知足開閉原則的。框架

小結

單獨說開閉原則是沒有什麼內容可講的,就像和你說「堅強的人能夠克服任何困難」。那麼其實咱們關心的是怎麼才能變得「堅強」。其餘的設計原則其實都是圍繞着怎麼實現開閉原則而進行的。this

2 單一職責原則

定義:

單一職責原則(Single Responsibility Principle, SRP):一個類只負責一個功能領域中的相應職責,或者能夠定義爲:就一個類而言,應該只有一個引發它變化的緣由。設計

解讀

通俗一點說,就是一個類不能承擔太多的功能。只專心一件事,而且把這件事作好。舉一個例子,假如要寫一個食堂類。對於客戶端來講,食堂核心功能是要能打飯和回收餐具。若是要打飯那麼確定須要有先把菜作好,若是要作菜還要有人去買菜。那麼若是這些功能都放在一個類裏面就會以下圖:
輸入圖片說明
很明顯違反了單一職責原則。爲何?明明就是隻負責吃相關職能啊。你都說了是吃相關,那麼確定就不僅是一個簡單功能。吃前要準備,吃後要收拾。其實做爲一個服務,就和咱們去食堂吃飯,我只關心怎麼打飯,我吃完之後把餐具放哪裏。其餘的至於菜市從哪裏買的,誰炒的都不關心。所以,改良一下
輸入圖片說明
做爲食堂,提供多種多樣的菜品(如川菜、粵菜)。若是讓一個廚師即炒粵菜又炒川菜又違背了單一職責原則。所以再改一下:
輸入圖片說明
單一職責好處就是: 能夠下降類的複雜度,提升類的可讀性,下降變動引發的風險下降。變動是必然的,若是單一職責原則遵照的好,當修改一個功能時,能夠顯著下降對其餘功能的影響。code

小結

其實單一職責不只僅是針對一個類的設計,往小的說一個方法、往大的說一個模塊都應該知足單一職責原則。只是怎麼去肯定職責及其範圍是須要根據具體的場景來肯定。對象

3 里氏代換原則

定義:

里氏代換原則(Liskov Substitution Principle, LSP):全部引用基類(父類)的地方必須能透明地使用其子類的對象。繼承

解讀

里氏代換原則核心就是要知足繼承體系,能用父類的地方,那麼其任何子類也能夠正確調用。不然,就不該該作爲其子類(或者父類的定義就不正確)。例如前面的例子中,廚師的子類有川菜廚師、粵菜廚師等,但不管把哪一個廚師放在廚房裏面確定都能作菜的。從代碼層面上來講:若是一個類實現一個接口,那麼就應該實現該接口的全部方法。比較經典的案例就是Spring的CacheManager的接口。
輸入圖片說明
CacheManager只是提供一個接口,核心功能就是存儲Cache。ConcurrentMapCacheManager就是把Cache存在內存Map中,EhCacheCacheManager就是使用EhCache框架。固然若是存在Redis中或者其餘存儲引擎中,只要實現該接口就能夠了。可是就是必須存儲cache及其與name的映射關係。接口

小結

若是你理解(或者使用過)面嚮對象語言,那麼里氏替換原則理解起來就十分簡單。例如java在編譯的時候就會檢查一個程序是否符合里氏替換原則。(但編譯器只能檢查無意的違反原則,但不能檢查故意的違反原則)雖然很簡單,可是這也是實現開閉原則的基本條件。試想,當擴展一個接口的時候,發現擴展類在實現該接口的方法不能正確調用,那麼這個該擴展也是沒有任何意義的。圖片

4 依賴倒轉原則

定義:

依賴倒轉原則(Dependency Inversion Principle, DIP):抽象不該該依賴於細節,細節應當依賴於抽象。換言之,要針對接口編程,而不是針對實現編程。

解讀

上面定義中的抽象能夠簡單的理解爲接口,細節就是接口的實現類。也就是說,不管是定義變量、方法參數類型仍是方法返回都儘可能使用抽象接口,而不是返回具體的實現類。這樣作的目的就是方便之後擴展(也就是實現開閉原則的手段)。仍是前面的食堂爲例,假如一開始的時候只有MarketA賣菜,採購員就只有去哪裏賣菜。也許一開始的代碼就會這樣寫:

class MarketA {
    public String name() {
        return ("MarketA");
    }
}

class Buyer {
    public void buy(MarketA marketA) {
        System.out.println("採購員在" + marketA.name() + "買菜");
    }
}

class  Canteen{
    public void buyFood() {
        Buyer buyer = new Buyer();
        buyer.buy(new MarketA());
    }
}

類圖關係以下:
輸入圖片說明 忽然有一天,MarketB開業,而且每週一的價格比MarketA便宜,所以,選擇週一在MarketB買菜。

class MarketA {
    public String name() {
        return ("MarketA");
    }
}


class MarketB {
    public String name() {
        return ("MarketB");
    }
}

class Buyer {
    public void buyA(MarketA marketA) {
        System.out.println("採購員在" + marketA.name() + "買菜");
    }

    public void buyB(MarketB marketB) {
        System.out.println("採購員在" + marketB.name() + "買菜");
    }
}
class  Canteen{
    public void buyFood() {
        Buyer buyer = new Buyer();
        // 其實這也也不知足單一職責原則,即要選擇菜市場,又要派採購員買菜。
        if(to is monday){
            buyer.buyA(new MarketA());
        }else{
           
            buyer.buyB(new MarketB());
        }
    }
}

類圖:

輸入圖片說明
忽然又有一天,MarketC,MarketD...MarketCX 開業了,而且....好吧,是否是發現這樣寫下去何時是個頭啊。所以咱們須要進行代碼重構,使其知足開閉原則。

interface Market {
    String name();
}

class MarketA implements Market {
    public String name() {
        return ("MarketA");
    }
}


class MarketB implements Market {
    public String name() {
        return ("MarketB");
    }
}

interface Buyer {
    public void buy(Market market);
}
//省略Buyer的子類
    
class  Canteen{    
    public void CanteenBuyFood() {
        Market market = selectMarket();
        Buyer buyer = selectBuyer();
        buyer.buy(market);
    }
    
    private Buyer selectBuyer() {
        //根據實際狀況選擇採購員
    }
    
    private Market selectMarket() {
        // 根據狀況選擇市場  
    }
}

類圖:
輸入圖片說明
重構後代碼是否是不管加多少market,只要修改selectMarket()的方法。不管加多少採購員,只要修改Buyer.getBuyer()中的代碼。

小結

能夠看出,在代碼重構過程當中,在大多數狀況下開閉原則、里氏代換原則和依賴倒轉原則這三個設計原則會同時出現。開閉原則是目標,里氏代換原則是基礎,依賴倒轉原則是手段,他們目的都是爲了代碼的擴展性,只是分析問題時所站角度不一樣而已。

5 接口隔離原則

定義:

接口隔離原則(Interface Segregation Principle, ISP):使用多個專門的接口,而不使用單一的總接口,即客戶端不該該依賴那些它不須要的接口。

解讀

接口隔離就是定義一個接口的時候不要定義得太而廣,而是把他們分割成一些更細小的接口。通常不知足單一職責原則都不知足接口隔離原則,這樣的例子不少,就很少說明了。但反過來卻不必定,舉一個例子(每每原則的例子舉反例是最清晰的)。仍是買菜的問題,若是咱們定義一個市場,在市場裏面買肉、買蔬菜、買水果....),每每一開始咱們定義接口的時候是按照一個樣例來定義接口,好比上面就是按照一個超級市場來定義的接口,其實這樣作是很正常的狀況。

interface Market {
    void buyMeat();

    void buyVegetables();
}

class SuperMarket implements Market {

    public void buyMeat() {
        System.out.println("買肉");
    }

    public void buyVegetables() {
        System.out.println("買菜");
    }
}

這樣定義接口在一開始是沒問題,可是隨着業務擴展,若是如今存在另一個市場,他只賣菜,不賣肉。。那麼該類在 buyMeat()下面怎麼辦?當你發現你實現一個接口的時候,某個須要實現的方法你沒辦法去實現,這種狀況也算不知足接口隔離的原則了。這時候就須要進行重構,把接口進一步細化,例如,把Martet細化爲MeatMarket和VegetableMarket。

interface Market {
    //其餘共有方法
}

interface MeatMarket extends Market{
    void buyMeat();
}

interface VegetableMarket extends Market{
    void buyVegetables();
}

class SuperMarket implements MeatMarket,VegetableMarket {

    public void buyMeat() {
        System.out.println("買肉");
    }

    public void buyVegetables() {
        System.out.println("買菜");
    }
}

class SmallMarket implements VegetableMarket {

    public void buyVegetables() {
        System.out.println("買菜");
    }
}

類圖:
輸入圖片說明
上面的實例代碼僅僅是爲了說明接口隔離原則,

小節

接口隔離原則核心思想就是細化接口,提升程序的靈活性。但細化到什麼程度卻沒有具體的度量,接口不能過小,若是過小會致使系統中接口氾濫,反而不利於維護。所以如何把握這個讀就是經驗了。

迪米特法則

定義

迪米特法則(Law of Demeter, LoD):一個軟件實體應當儘量少地與其餘實體發生相互做用。

解讀

迪米特法則還有幾種定義形式,包括:不要和「陌生人」說話、只與你的直接朋友通訊等

一個類只與他的朋友類進行交互

所謂一個類的朋友,就是該類

  1. 當前對象自己(this);
  2. 以參數形式傳入到當前對象方法中的對象,或者返回的方法返回的對象
  3. 當前對象的成員對象;

從代碼層面上講,當該類的全部方法體爲空的時候,該類所依賴的類就是朋友類 所謂只與他朋友類進行交互意思就比較好理解了,就是不能直接調用他朋友的朋友方法。也就是在下圖中: 輸入圖片說明
食堂服務==朋友==>食堂後勤==朋友==>廚師,咱們不能直接在食堂服務中直接調用讓廚師去炒菜。食堂服務類要與廚師類通訊,也是必需要有食堂後勤類做爲中介。從代碼層面上來講,通常 A.getB().getC().xxxMethod() 每每都是破壞迪米特法則。

即便是朋友也要保持距離

一個類公開的public屬性或方法越多,修改時涉及的面也就越大,變動引發的風險擴散也就越大。例如,

class A {
    public void step1(){
        //do something
    }

    public void step2(){
        //do something}
    }

    public void step3(){
        //do something}
    }
}
class M {
    public void someCall(A a) {
        a.step1();
        a.step2();
        a.step3();
    }
}

從代碼裏面看,A確實是M的朋友,可是,M和A太"親密"了,若是之後須要在調用step2的以前調用一個check2()的方法,就必需要修改M中的方法,若是這個調用方式大量出如今工程中,就會引發擴散。若是我以前是這麼設計的交互的方式就不會存在這個問題。也就是說,朋友之間的交互不要太"親密"。同時做爲別人朋友,最好能提供「一站式服務」。

class A {
    private void step1(){
        //do something
    }

    private void step2(){
        //do something}
    }

    private void step3(){
        //do something}
    }
    
    public void exe(){
        step1();
        step2();
        step3();
    }
}
class M {
    public void someCall(A a) {
       a.exe();
    }
}

小節

迪米特發展的目的就是下降系統的耦合度,使類與類之間保持鬆散的耦合關係。和接口隔離原則同樣,迪米特法則也是一個沒法進行度量。過分使用迪米特法則,也會形成系統的不一樣模塊之間的通訊效率下降,這就直接致使了系統中存在大量的中介類。所以,如何使用迪米特法則仍是那句話,根據經驗。

總結

單一職責原則告訴咱們實現類的功能不要太多。里氏替換原則告訴咱們不要破壞繼承體系;依賴倒置原則告訴咱們要面向接口編程;接口隔離原則告訴咱們在設計接口的時候要精簡單一;迪米特法則告訴咱們要下降耦合。而開閉原則總的大綱,要對擴展開放,對修改關閉。 設計原則只是給咱們一些指導性的意見,在實際工做中每每要根據實際狀況來判斷。就如開篇所說的那樣,每每菜譜每每給咱們的意見都是「少量」,「適量」,而真正要烹飪出一道美味還須要咱們本身不斷去積累和調整。

相關文章
相關標籤/搜索