為什么需要設(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店老板眼里,只要新來的能在銷售崗位上像銷售老手一樣賣出汽車,他就是一名合格的銷售。感覺這種定義就像一句名言:不管你黑貓白貓,能抓老鼠的都是好貓。
從某種含義上里氏代換有著以下的契約:
- 子類必須完全實(shí)現(xiàn)父類的方法。父類出現(xiàn)的地方子類都可以代替父類。
- 子類可以有自己的個性定義。 里氏替換原則 可以正著用,但是不能反過來用。在子類出現(xiàn)的地方,父類未必就可以勝任。子類一般比父類有個性。
- 覆蓋或?qū)崿F(xiàn)父類的方法時輸入?yún)?shù)可以被放大。如果4S店老板規(guī)定基礎(chǔ)車談價的折扣最多九折,銷售打個九五折沒有問題,打八折老板肯定要跟你說道說道了。
- 覆寫或?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)小胖哥,獲取更多資訊