大話設(shè)計(jì)模式
設(shè)計(jì)模式之禪
github我見過(guò)最好的設(shè)計(jì)模式
http://c.biancheng.net/view/1326.html
在設(shè)計(jì)的時(shí)候盡可能的考慮,需求的變化,新需求來(lái)了盡可能少的改動(dòng)代碼,擁抱變化
定義:指的是軟件中一個(gè)實(shí)體,如類、模塊和函數(shù)應(yīng)該對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉
。
面向抽象編程
開閉是對(duì)擴(kuò)展和修改的約束
強(qiáng)調(diào):用抽象構(gòu)建框架,用實(shí)現(xiàn)擴(kuò)展細(xì)節(jié)。
優(yōu)點(diǎn):提高軟件系統(tǒng)的可復(fù)用性及可維護(hù)性
a*b*c
變化成a*b+c
其實(shí)是可以直接修改的,前提是所有依賴或者關(guān)聯(lián)類都按照相同的邏輯來(lái)處理頂層接口
接口是規(guī)范,抽象是實(shí)現(xiàn)
通過(guò)繼承來(lái)解決
價(jià)格的含義已經(jīng)變化了,所以不能夠子類直接繼承getPrice()
,因?yàn)楫?dāng)前已經(jīng)是折扣價(jià)格了,可能需要價(jià)格和折扣價(jià)格
為什么要遵循開閉原則,從軟件工程角度怎么理解這點(diǎn)。
- 開閉原則對(duì)擴(kuò)展開放對(duì)修改關(guān)閉,程序和需求一定是不斷修改的,我們需要把共性和基礎(chǔ)的東西抽出來(lái),把常常修改的東西讓他能夠擴(kuò)展出去,這樣我們程序后期維護(hù)的風(fēng)險(xiǎn)就會(huì)小很多
高層模塊不應(yīng)該依賴低層模塊,二者都應(yīng)該依賴其抽象。抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象。
說(shuō)白了就是針對(duì)接口編程,不要針對(duì)實(shí)現(xiàn)編程
public class DipTest {
public static void main(String[] args) {
//===== V1 ========
// Tom tom = new Tom();
// tom.studyJavaCourse();
// tom.studyPythonCourse();
// tom.studyAICourse();
//===== V2 ========
// Tom tom = new Tom();
// tom.study(new JavaCourse());
// tom.study(new PythonCourse());
//===== V3 ========
// Tom tom = new Tom(new JavaCourse());
// tom.study();
//===== V4 ========
Tom tom = new Tom();
tom.setiCourse(new JavaCourse());
tom.study();
}
}
以抽象為基準(zhǔn)比以細(xì)節(jié)為基準(zhǔn)搭建起來(lái)的架構(gòu)要穩(wěn)定得多,因此大家在拿到需求之后, 要面向接口編程,先頂層再細(xì)節(jié)來(lái)設(shè)計(jì)代碼結(jié)構(gòu)。
為什么要依賴抽象,抽象表示我還可以擴(kuò)展還沒有具體實(shí)現(xiàn),按照自己的話來(lái)解釋一遍
- 一般軟件中抽象分成兩種,接口和抽象類,接口是規(guī)范,抽象是模板,我們通過(guò)抽象的方式,也就是使用規(guī)范和模板這樣我們能夠使得上層,也就是調(diào)用層能夠復(fù)用邏輯,而我們底層是能夠快速更改實(shí)現(xiàn)的,例如Spring的依賴注入,Dubbo的SPI,SpringBoot的SPI都如此
不要存在多余一個(gè)導(dǎo)致類變更的原因
只負(fù)責(zé)一項(xiàng)職責(zé)
如果不是這樣設(shè)計(jì),一個(gè)接口負(fù)責(zé)兩個(gè)職責(zé),一旦需求變更,修改其中一個(gè)職責(zé)的邏輯代碼會(huì)導(dǎo)致另外一個(gè)職責(zé)的功能發(fā)生故障。
上述圖片用戶的屬性和用戶的行為并沒有分開
電話通話會(huì)發(fā)生下面四個(gè)過(guò)程
上圖的接口做了兩個(gè)事情
引起變化的點(diǎn)
打電話
,上網(wǎng)
從上面可以看到包含了兩個(gè)職責(zé),應(yīng)該考慮拆分成兩個(gè)接口
單一職責(zé)原則提出了一個(gè)編寫程序的標(biāo)準(zhǔn),用“職責(zé)”或“變 化原因”來(lái)衡量接口或類設(shè)計(jì)得是否優(yōu)良,但是“職責(zé)”和“變化原因”都 是不可度量的,因項(xiàng)目而異,因環(huán)境而異。
This is sometimes hard to see
,單一職責(zé)確實(shí)收到很多因素制約
- 工期
- 成本
- 技術(shù)水平
- 硬件情況
- 網(wǎng)絡(luò)情況
- 政府政策
不要建立龐大臃腫的接口
高內(nèi)聚低耦合
為什么要把IAnimal拆分成IFlyAnimal,ISwimAnimal,不拆分會(huì)有什么樣的問(wèn)題
- 一個(gè)類所提供的功能應(yīng)該是他所真正具有的,不拆分會(huì)導(dǎo)致他不提供的功能但是強(qiáng)行需要實(shí)現(xiàn),而且會(huì)有臃腫的類出現(xiàn)
- 可能適配器模式也是為了解決這個(gè)問(wèn)題吧
適配器模式
進(jìn)行轉(zhuǎn)化處理;一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象保證最少的了解,也稱最少知道原則
,如果兩個(gè)類不必彼此直接通信,那么這兩個(gè)類就不應(yīng)該發(fā)生直接的相互作用,如果其中一個(gè)類需要調(diào)用另外一個(gè)類的某個(gè)方法的話,可以通過(guò)第三者轉(zhuǎn)發(fā)這個(gè)調(diào)用
能夠降低類與類之間的耦合
強(qiáng)調(diào)只和朋友交流
出現(xiàn)在成員變量、方法的輸入、輸出參數(shù)中的類都可以稱之為成員朋友類, 而出現(xiàn)在方法體內(nèi)部的類不屬于朋友類。
這里面感覺有點(diǎn)職責(zé)分開的感覺,不同的對(duì)象應(yīng)該關(guān)注不同的內(nèi)容,所做的事情也應(yīng)該是自己所關(guān)心的
teamLeader只關(guān)心結(jié)果,不關(guān)心Course
錯(cuò)誤類圖如下
如果以后你要寫代碼和重構(gòu)代碼你怎么分析怎么重構(gòu)?
- 先分析相應(yīng)代碼的職責(zé)
- 把不同的對(duì)象需要關(guān)心的內(nèi)容抽離出來(lái)
- 每個(gè)對(duì)象應(yīng)該只創(chuàng)建和關(guān)心自己所關(guān)心的部分
- 一定要使用的話可以通過(guò)三方來(lái)使用
- 合適的使用作用域,不要暴露過(guò)多的公共方法和非靜態(tài)的公共方法
迪米特法則要求類“羞澀”一點(diǎn),盡量不要對(duì)外公布太多的 public方法和非靜態(tài)的public變量,盡量?jī)?nèi)斂,多使用private、packageprivate、protected等訪問(wèn)權(quán)限。
在實(shí)際的項(xiàng)目中,需要適度地考慮這個(gè)原則,別為了套用原則而做項(xiàng)目。原則只是供參考,如果 違背了這個(gè)原則,項(xiàng)目也未必會(huì)失敗,這就需要大家在采用原則時(shí)反復(fù) 度量,不遵循是不對(duì)的,嚴(yán)格執(zhí)行就是“過(guò)猶不及”。
?如果 一個(gè)方法放在本類中,既不增加類間關(guān)系,也對(duì)本類不產(chǎn)生負(fù)面影響, 那就放置在本類中。
一個(gè)軟件實(shí)體如果能夠適用一個(gè)父親的話,那么一定適用其子類,所有引用父親的地方必須能透明的使用其子類的對(duì)象,子類能夠替換父類對(duì)象
寬松
嚴(yán)格或者相等
價(jià)格不是直接重寫,而是新寫一個(gè)方法
public class JavaDiscountCourse extends JavaCourse {
public JavaDiscountCourse(Integer id, String name, Double price) {
super(id, name, price);
}
public Double getDiscountPrice(){
return super.getPrice() * 0.61;
}
}
public static void resize(Rectangle rectangle){
while (rectangle.getWidth() >= rectangle.getHeight()){
rectangle.setHeight(rectangle.getHeight() + 1);
System.out.println("Width:" +rectangle.getWidth() +",Height:" + rectangle.getHeight());
}
System.out.println("Resize End,Width:" +rectangle.getWidth() +",Height:" + rectangle.getHeight());
}
public class Square extends Rectangle {
private long length;
//勝率
@Override
public void setHeight(long height) {
setLength(height);
}
}
當(dāng)前設(shè)計(jì)會(huì)出現(xiàn)死循環(huán)
抽象接口
public interface QuadRangle {
long getWidth();
long getHeight();
}
返回共同的length
public class Square implements QuadRangle {
private long length;
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
public long getWidth() {
return length;
}
public long getHeight() {
return length;
}
}
當(dāng)前方式子類就能夠隨時(shí)替換父類了
- 你怎么理解里氏替換原則,為什么要保證使用父類的地方可以透明地使用子類
- 子類必須實(shí)現(xiàn)父類中沒有實(shí)現(xiàn)的方法
- is-a的問(wèn)題
- 如果父類的地方替換成子類不行的話程序復(fù)雜性增加,繼承反而帶來(lái)了程序的復(fù)雜度
- 子類只能在父類的基礎(chǔ)上增加新的方法
- 在具體場(chǎng)景中怎么保證使用父類的地方可以透明地使用子類
- 父類返回多使用具體實(shí)現(xiàn),入?yún)⒍嗍褂贸橄蠡蛘哒f(shuō)頂層接口
- 子類可以新增一些自己特有的方法
如果子類不能完整地實(shí)現(xiàn)父類的方法,或者父類的某些方法 在子類中已經(jīng)發(fā)生“畸變”,則建議斷開父子繼承關(guān)系,采用依賴、聚 集、組合等關(guān)系代替繼承。
盡量避免子類的“個(gè)性”,一旦子 類有“個(gè)性”,這個(gè)子類和父類之間的關(guān)系就很難調(diào)和了,把子類當(dāng)做父 類使用,子類的“個(gè)性”被抹殺——委屈了點(diǎn);把子類單獨(dú)作為一個(gè)業(yè)務(wù) 來(lái)使用,則會(huì)讓代碼間的耦合關(guān)系變得撲朔迷離——缺乏類替換的標(biāo) 準(zhǔn)。
盡可能使用對(duì)象組合 has-a組合 或者是 contains-a聚合而不是通過(guò)繼承來(lái)達(dá)到軟件復(fù)用的目的。
為什么要多用組合和聚合少用繼承
- 繼承是侵入性的
- Java只支持單繼承
- 降低了代碼的靈活性,子類多了很多約束
- 增強(qiáng)了耦合性,父類修改的時(shí)候需要考慮子類的修改
- 會(huì)導(dǎo)致關(guān)鍵代碼被修改
如果你只有一把鐵錘, 那么任何東西看上去都像是釘子。
我的筆記倉(cāng)庫(kù)地址gitee 快來(lái)給我點(diǎn)個(gè)Star吧
聯(lián)系客服