白話設(shè)計(jì)模式原則

為什么需要設(shè)計(jì)模式

其實(shí)沒有設(shè)計(jì)模式我們也能完成開發(fā)工作。但是為什么需要設(shè)計(jì)模式呢?讓你看起來很牛,沒錯這個算一個。讓你的代碼層次感分明,可讀性強(qiáng)而且容易維護(hù)。讓你像我一樣有更多的摸魚劃水時間。

可能有人說我一個類或者方法就干完的東西,你搞了七八個。當(dāng)然使用設(shè)計(jì)模式也是要斟酌的。一些簡單穩(wěn)定的業(yè)務(wù)也不推薦使用設(shè)計(jì)模式。設(shè)計(jì)模式多用于復(fù)雜多變的業(yè)務(wù)或者要求適配性、擴(kuò)展性更強(qiáng)的場景中。不要為了設(shè)計(jì)模式而設(shè)計(jì)模式。

接下來我們結(jié)合實(shí)際開探討一下設(shè)計(jì)模式的一些原則。

開閉原則


public class Seller {


    public BigDecimal sellCar(Car car) {
        return car.getPrice();
    }

}

上面模擬4S店一個銷售在賣車。突然老板搞了一個促銷:在雙十一要開展打折活動。在sellCar方法內(nèi)增加一個計(jì)算可行嗎?這勢必影響整個業(yè)務(wù),導(dǎo)致所有車都打折。不行不行!那么在Car里面操作?然后你改啊改!結(jié)果各種邏輯流程判斷。才實(shí)現(xiàn)了業(yè)務(wù)要求。如果后續(xù)打折活動結(jié)束了或者升級了,你還要再進(jìn)行各種改動。你發(fā)現(xiàn)一個打折讓你的代碼面目全非、臃腫不堪。上面說了對于復(fù)雜而多變的業(yè)務(wù)使用設(shè)計(jì)模式就可以解決。那么設(shè)計(jì)模式最重要的一個原則就是開閉原則。也就是說
一個軟件模型實(shí)體如類、模塊和函數(shù)應(yīng)該對擴(kuò)展開放,對修改關(guān)閉。也就是需要我們將業(yè)務(wù)行為抽象出來,使用抽象來構(gòu)建。具體的業(yè)務(wù)通過抽象的實(shí)現(xiàn)來解決。那么我們就搞一個DiscountCar來extends Car.這樣sellCar是什么具體的實(shí)現(xiàn)就執(zhí)行什么具體的邏輯。不會影響以前的邏輯,而且不會因?yàn)楦膭釉瓉淼拇a影響其他邏輯。保證接口可靠性和穩(wěn)定性。如下:

 public class DiscountCar extends Car{
        
    private BigDecimal price;
    private BigDecimal discount;
 
    @Override
    public BigDecimal getPrice() {
        return price.multiply(discount);
    }
 
 }

依賴倒置原則

還拿上面的例子來說。經(jīng)過一系列的打折活動4S店的生意蒸蒸日上。老板突然想擴(kuò)展一下周邊,同時壓榨一下銷售。讓他們賣車的同時賣點(diǎn)玻璃水、防凍液之類的。這個需求當(dāng)然又拋給了苦逼的程序員。sellCar太具體了不能滿足需要了。很多情況下你會增加一個賣玻璃水、賣防凍液的方法。如果以后增加了賣大米,甚至買起了雞蛋餅?zāi)??總不能一直增加方法吧。我們需要考慮這種問題。我們可以抽象所有賣東西的場景。然后我們把賣的物品抽象成了一個抽象化的概念(java對應(yīng)的是接口,把賣的行為抽象成了sell方法:


public interface Any {

    String getName();

    BigDecimal getPrice();

}


public class Seller {

    public BigDecimal sell(Any any) {
        return any.getPrice();
    }
}

這樣隨便老板以后賣什么你都可以通過該方法進(jìn)行處理了,只需要關(guān)注于Any的實(shí)現(xiàn)。

職責(zé)單一

4S店銷售賣了一段東西后,發(fā)現(xiàn)對客戶的吸引力度不大。突然腦子比較靈活的老板又想起了電影中的一句臺詞:少林功夫加唱歌跳舞有沒有搞頭? 對啊你們銷售能不能搞搞什么唱、跳、Rap,當(dāng)然打籃球就不要了別砸壞了車玻璃。但是人與人是不一樣的,有的人只會唱,有的人只會跳,有的人可能唱跳Rap都會甚至籃球都很溜。所以為了適配這么多情況,我們必須把每種技能獨(dú)立出來,根據(jù)不同的人來組合這些技能。


public class Seller implements Sing, Jump, Rap {


    public BigDecimal sell(Any any) {
        return any.doSell();
    }

    @Override
    public void sing() {
        System.out.println("seller sing ");
    }

    @Override
    public void jump() {
        System.out.println("seller jumping ");
    }

    @Override
    public void rap() {
        System.out.println("seller raping ");
    }
}

但是注意一定要適度,根據(jù)業(yè)務(wù)來細(xì)分。否則會導(dǎo)致接口過多反而增大開發(fā)難度以及代碼的復(fù)雜度。

迪米特法則

新的銷售方法搞了一段時間后,老板想看看檢驗(yàn)一下他這個餿主意的效果。于是就叫了一個銷售讓他提供一份報表出來看看。那么程序員該如何實(shí)現(xiàn)老板查看報表功能呢,很可能有人會這么寫:

public class Boss {
    private Seller seller;
    private Report report;


    public void read() {
        seller.apply(report);
    }
}

乍看功能實(shí)現(xiàn)了,細(xì)看會發(fā)現(xiàn)邏輯不對。哪里不對呢?老板已經(jīng)持有了報表,如果老板已經(jīng)知道了你的業(yè)績還叫你干什么?這種邏輯肯定是不對的!也就是說Boss直接依賴了Report;而這個Report不應(yīng)該直接由Boss處理,而應(yīng)由Seller控制。

public class Boss {
    private Seller seller;
    
    public void read(){
         seller.apply();
    }
    
}

public class Seller {
      private Report report;
      
      public void apply(){
         report.show();
      }

}


這種最大化隔離了類與類之間的關(guān)系。降低了類之間的耦合。迪米特法則因此又得名最少知道原則。

接口隔離原則

用多個專門的接口,而不使用單一的總接口,客戶端不應(yīng)該依賴它不需要的接口。一個類對一個類的依賴應(yīng)該建立在最小的接口上。

建立單一接口,不要建立龐大臃腫的接口盡量細(xì)化接口,接口中的方法盡量少,盡量細(xì)化接口。注意適度原則,一定要適度。不能濫用
就像上面的唱跳 rap,分離是最好的。

里氏代換原則

這里主要針對類的繼承關(guān)系而言。比較正式的定義:如果對每一個類型為S的對象o1,都有類型為T的對象o2,使得以T定義的所有程序P在所有的對象o1都代換成o2 時,程序P的行為沒有發(fā)生變化,那么類型 S 是類型 T 的子類型。
在4S店老板眼里,只要新來的能在銷售崗位上像銷售老手一樣賣出汽車,他就是一名合格的銷售。感覺這種定義就像一句名言:不管你黑貓白貓,能抓老鼠的都是好貓。

從某種含義上里氏代換有著以下的契約:

  1. 子類必須完全實(shí)現(xiàn)父類的方法。父類出現(xiàn)的地方子類都可以代替父類。
  2. 子類可以有自己的個性定義。 里氏替換原則 可以正著用,但是不能反過來用。在子類出現(xiàn)的地方,父類未必就可以勝任。子類一般比父類有個性。
  3. 覆蓋或?qū)崿F(xiàn)父類的方法時輸入?yún)?shù)可以被放大。如果4S店老板規(guī)定基礎(chǔ)車談價的折扣最多九折,銷售打個九五折沒有問題,打八折老板肯定要跟你說道說道了。
  4. 覆寫或?qū)崿F(xiàn)父類的方法時輸出結(jié)果可以被縮小。同樣是15W本來只能賣出給客戶一個乞丐版,結(jié)果換了個銷售結(jié)果給出了一輛旗艦版。怕不是過不了試用期哦。

合成/復(fù)用原則

它要求在軟件復(fù)用時,要盡量先使用組合或者聚合等關(guān)聯(lián)關(guān)系來實(shí)現(xiàn),其次才考慮使用繼承關(guān)系來實(shí)現(xiàn)。
如果要使用繼承關(guān)系,則必須嚴(yán)格遵循里氏替換原則。合成復(fù)用原則同里氏替換原則相輔相成的,兩者都是開閉原則的具體實(shí)現(xiàn)規(guī)范。

總結(jié)

這七種設(shè)計(jì)原則是軟件設(shè)計(jì)模式必須盡量遵循的原則,各種原則要求的側(cè)重點(diǎn)不同。其中,開閉原則是總綱,它告訴我們要對擴(kuò)展開放,對修改關(guān)閉;里氏替換原則告訴我們不要破壞繼承體系;依賴倒置原則告訴我們要面向接口編程;單一職責(zé)原則告訴我們實(shí)現(xiàn)類要職責(zé)單一;接口隔離原則告訴我們在設(shè)計(jì)接口的時候要精簡單一;迪米特法則告訴我們要降低耦合度;合成復(fù)用原則告訴我們要優(yōu)先使用組合或者聚合關(guān)系復(fù)用,少用繼承關(guān)系復(fù)用。在實(shí)際開發(fā)中我們可以根據(jù)業(yè)務(wù)來進(jìn)行設(shè)計(jì)模式的使用,但是很重要的一點(diǎn)千萬不要被這些條條框框束縛了你的手腳。

關(guān)注公眾號:碼農(nóng)小胖哥,獲取更多資訊

個人博客:https://felord.cn

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 目錄: 設(shè)計(jì)模式六大原則(1):單一職責(zé)原則 設(shè)計(jì)模式六大原則(2):里氏替換原則 設(shè)計(jì)模式六大原則(3):依賴倒...
    加油小杜閱讀 800評論 0 1
  • 轉(zhuǎn)載標(biāo)注聲明:http://www.uml.org.cn/sjms/201211023.asp 目錄:[設(shè)計(jì)模式六...
    Bloo_m閱讀 799評論 0 7
  • 什么是設(shè)計(jì)模式 在GoF(Gang of Four)的書籍《Design Patterns - Elements ...
    zhrowable閱讀 1,194評論 0 1
  • 設(shè)計(jì)模式六大原則 設(shè)計(jì)模式六大原則(1):單一職責(zé)原則 定義:不要存在多于一個導(dǎo)致類變更的原因。通俗的說,即一個類...
    viva158閱讀 826評論 0 1
  • 以前的那些朋友啊 不知道怎么就走散了 可能是因?yàn)榇蠹胰チ瞬煌膶W(xué)校 接觸了不同的人 也大概是由于大家都在忙著為自己...
    4d969fba04f9閱讀 168評論 0 1

友情鏈接更多精彩內(nèi)容