軟件設(shè)計(jì)知識(shí)是一名軟件開發(fā)人員必須要懂的知識(shí),最近幾天今天看了bob大叔的《敏捷軟件開發(fā)》一書和軟件設(shè)計(jì)相關(guān)的一些blog和資料并炮,自己做了一個(gè)學(xué)習(xí)筆記
設(shè)計(jì)目標(biāo)
正確性呵哨、健壯性、靈活性、可重用性屿愚、高效性
降低復(fù)雜性
- 所謂復(fù)雜性,就是任何使得軟件難于理解和修改的因素务荆。
-
復(fù)雜性的來(lái)源主要有兩個(gè):代碼的含義模糊和互相依賴
- 模糊指的是妆距,代碼里的重要信息看不出來(lái);
- 依賴指的是函匕,某個(gè)模塊的代碼娱据,不結(jié)合其他模塊的代碼,就無(wú)法理解
危害:復(fù)雜性的危害在于盅惜,它會(huì)遞增中剩。如果做錯(cuò)了一個(gè)決定,導(dǎo)致后面的代碼都基于前面的錯(cuò)誤實(shí)現(xiàn)抒寂,只會(huì)越來(lái)越復(fù)雜结啼。"常聽人說(shuō),我們先把產(chǎn)品做出來(lái)屈芜,后面再改進(jìn)"郊愧,這很難做到。
==關(guān)鍵==:找出易變化的部分井佑,合理抽象属铁。
用抽象構(gòu)建框架、用實(shí)現(xiàn)擴(kuò)展細(xì)節(jié)
代碼抽象三原則:
1. Don’t Repeat Yourself (DRY)
- 系統(tǒng)的每一個(gè)功能都應(yīng)該有唯一的實(shí)現(xiàn)躬翁。也就是說(shuō)焦蘑,如果多次遇到同樣的問(wèn)題,就應(yīng)該抽象出一個(gè)共同的解決方法盒发,不要重復(fù)開發(fā)同樣的功能
不要重復(fù)自己
2. You Ain’t Gonna Need It (YAGNI)
- 定義:你不會(huì)需要它喇肋,只考慮和設(shè)計(jì)必須的功能坟乾,避免過(guò)度設(shè)計(jì)
- 這是"極限編程"提倡的原則,指的是你自以為有用的功能蝶防,實(shí)際上都是用不到的甚侣。因此,除了最核心的功能间学,其他功能一概不要部署殷费,這樣可以大大加快開發(fā)
- 你會(huì)發(fā)現(xiàn)DRY原則和YAGNI原則并非完全兼容。前者追求"抽象化"低葫,要求找到通用的解決方法详羡;后者追求"快和省"
3.Rule Of Three
稱為"三次原則",指的是當(dāng)某個(gè)功能第三次出現(xiàn)時(shí)嘿悬,才進(jìn)行"抽象化"实柠。
-
這樣做有幾個(gè)理由:
省事。如果一種功能只有一到兩個(gè)地方會(huì)用到善涨,就不需要在"抽象化"上面耗費(fèi)時(shí)間了窒盐。
容易發(fā)現(xiàn)模式。"抽象化"需要找到問(wèn)題的模式钢拧,問(wèn)題出現(xiàn)的場(chǎng)合越多蟹漓,就越容易看出模式,從而可以更準(zhǔn)確地"抽象化"源内。
防止過(guò)度冗余葡粒。如果一種功能同時(shí)有多個(gè)實(shí)現(xiàn),管理起來(lái)非常麻煩膜钓,修改的時(shí)候需要修改多處嗽交。在實(shí)際工作中,重復(fù)實(shí)現(xiàn)最多可以容忍出現(xiàn)一次颂斜,再多就無(wú)法接受了夫壁。
小結(jié):綜上所述,"三次原則"是DRY原則和YAGNI原則的折衷焚鲜,是代碼冗余和開發(fā)成本的平衡點(diǎn)掌唾,值得我們?cè)?抽象化"時(shí)遵循
面向?qū)ο蟮腟.O.L.I.D原則
1. SRP職責(zé)單一原則
- 核心思想是一個(gè)類只做一件事,把事情做好忿磅,其只有一個(gè)引起它變化的原因糯彬,職責(zé)過(guò)多,引起它變化的原因就越多葱她,將導(dǎo)致責(zé)任依賴增加耦合性
- 遵循單一職責(zé)原的優(yōu)點(diǎn)有:
- 可以降低類的復(fù)雜度撩扒,一個(gè)類只負(fù)責(zé)一項(xiàng)職責(zé),其邏輯肯定要比負(fù)責(zé)多項(xiàng)職責(zé)簡(jiǎn)單的多;
- 提高類的可讀性搓谆,提高系統(tǒng)的可維護(hù)性炒辉;
- 變更引起的風(fēng)險(xiǎn)降低,變更是必然的泉手,如果單一職責(zé)原則遵守的好黔寇,當(dāng)修改一個(gè)功能時(shí),可以顯著降低對(duì)其他功能的影響斩萌。
- 應(yīng)用場(chǎng)景:迭代器
2. 里氏替換原則
- 定義:所有引用基類的地方必須能透明地使用其子類的對(duì)象缝裤,替換之后,代碼還能正常工作颊郎。它是使代碼符合開閉原則的重要保證.
- 問(wèn)題:有一功能P1憋飞,由類A完成。現(xiàn)需要將功能P1進(jìn)行擴(kuò)展姆吭,擴(kuò)展后的功能為P榛做,其中P由原有功能P1與新功能P2組成。新功能P由類A的子類B來(lái)完成内狸,則子類B在完成新功能P2的同時(shí)检眯,有可能會(huì)導(dǎo)致原有功能P1發(fā)生故障
- 解決:當(dāng)使用繼承時(shí),遵循里氏替換原則答倡。類B繼承類A時(shí)轰传,除添加新的方法完成新增功能P2外驴党,盡量不要重寫父類A的方法瘪撇,也盡量不要重載父類A的方法
- 換個(gè)說(shuō)法是,子類可以擴(kuò)展父類的功能港庄,但不能改變父類原有的功能(不能破壞繼承體系)
- 子類可以實(shí)現(xiàn)父類的抽象方法倔既,但不能覆蓋父類的非抽象方法
- 子類中可以增加自己特有的方法
- 當(dāng)子類的方法重載父類的方法時(shí),方法的前置條件(即方法的形參)要比父類方法的輸入?yún)?shù)更寬松鹏氧。
- 當(dāng)子類的方法實(shí)現(xiàn)父類的抽象方法時(shí)渤涌,方法的后置條件(即方法的返回值)要比父類更嚴(yán)格
3. 接口隔離原則
- 定義:客戶端不應(yīng)該依賴它不需要的接口;一個(gè)類對(duì)另一個(gè)類的依賴應(yīng)該建立在最小的接口上把还。
- 規(guī)約:建立單一接口实蓬,不要建立龐大臃腫的接口,盡量細(xì)化接口吊履,接口中的方法盡量少安皱,但不要過(guò)度。也就是說(shuō)艇炎,我們要為各個(gè)類建立專用的接口酌伊,而不要試圖去建立一個(gè)很龐大的接口供所有依賴它的類去調(diào)用。接口是設(shè)計(jì)時(shí)對(duì)外部設(shè)定的“契約”缀踪,通過(guò)分散定義多個(gè)接口居砖,可以預(yù)防外來(lái)變更的擴(kuò)散虹脯,提高系統(tǒng)的靈活性和可維護(hù)性。
- 示例:人們對(duì)電腦有不同的使用方式:如上網(wǎng)奏候、看電影循集、寫文檔、玩游戲蔗草、通訊暇榴、計(jì)算和存儲(chǔ)等.如果都把這些功能都定義在電腦的抽象類里面。那各種功能類型的電腦(如上網(wǎng)本蕉世、服務(wù)器蔼紧、PC、智能學(xué)習(xí)機(jī))都要現(xiàn)實(shí)這些接口狠轻。所以奸例,應(yīng)該把這些接口隔離開,這樣不同功能的電腦只需要實(shí)現(xiàn)自己需要的接口.
4. 依賴倒置原則
- 定義:高層模塊不應(yīng)該依賴底層模塊的實(shí)現(xiàn)向楼,而是依賴高層抽象查吊。而且,二者都應(yīng)該依賴于抽象湖蜕。抽象不應(yīng)該依賴細(xì)節(jié)逻卖;細(xì)節(jié)應(yīng)該依賴抽象
- 問(wèn)題:類A直接依賴類B,假如要將類A改為依賴類C昭抒,則必須通過(guò)修改類A的代碼來(lái)達(dá)成评也。這種場(chǎng)景下,類A一般是高層模塊灭返,負(fù)責(zé)復(fù)雜的業(yè)務(wù)邏輯盗迟;類B和類C是低層模塊,負(fù)責(zé)基本的原子操作熙含;假如修改類A罚缕,會(huì)給程序帶來(lái)不必要的風(fēng)險(xiǎn)
- 解決:將類A修改為依賴接口I,類B和類C各自實(shí)現(xiàn)接口I怎静,類A通過(guò)接口I間接與類B或者類C發(fā)生聯(lián)系邮弹,則會(huì)大大降低修改類A的幾率
- 核心思想:面向接口編程,在java中蚓聘,抽象指的是接口或者抽象類腌乡,細(xì)節(jié)就是具體的實(shí)現(xiàn)類,使用接口或者抽象類的目的是制定好規(guī)范和契約或粮,而不去涉及任何具體的操作导饲,把展現(xiàn)細(xì)節(jié)的任務(wù)交給他們的實(shí)現(xiàn)類去完成。
- 好處:可以降低類之間的耦合性,提高系統(tǒng)的穩(wěn)定性渣锦,降低修改程序造成的風(fēng)險(xiǎn)硝岗。
- 應(yīng)用模式:工廠模式
- 規(guī)范:實(shí)際編程中,最好做到如下3點(diǎn):
- 低層模塊盡量都要有抽象類或接口袋毙,或者兩者都有型檀。
- 變量的聲明類型盡量是抽象類或接口。
- 使用繼承時(shí)遵循里氏替換原則
如果依賴的是一個(gè)穩(wěn)定的具體類听盖,那么可以直接依賴它
- 層次化
-
方式1:高層模塊依賴底層實(shí)現(xiàn)模塊胀溺,這種依賴性是傳遞的
依賴倒置1.png
-
-
方式2
- 解除傳遞依賴關(guān)系
- 接口所有權(quán)倒置:客戶擁有抽象接口,它的服務(wù)者從這些抽象接口中派生皆看,底層模塊實(shí)現(xiàn)了在高層模塊聲明并被高層模塊調(diào)用的接口
依賴倒置2.png
5. 開放/封閉原則
- 對(duì)擴(kuò)展開放仓坞,對(duì)修改關(guān)閉。如果有新的需求和變化可以對(duì)現(xiàn)有代碼進(jìn)行擴(kuò)展腰吟,以適應(yīng)新的情況.而不是對(duì)原有代碼進(jìn)行修改.
- 解決:關(guān)鍵在于抽象无埃、預(yù)測(cè)和刺激變化
- 應(yīng)用模式:裝飾者模式(如java.io包),不改變?cè)写a擴(kuò)展對(duì)象的行為
其他原則
1. Keep it sample,stupid(Kiss)
- 保持簡(jiǎn)單毛雇、直接嫉称,不要復(fù)雜化
- 模塊分成接口和實(shí)現(xiàn)。接口要簡(jiǎn)單灵疮,實(shí)現(xiàn)可以復(fù)雜织阅。
- 好的 class 應(yīng)該是"小接口,大功能",大量的功能隱藏在簡(jiǎn)單接口之下震捣,對(duì)用戶不可見荔棉,用戶感覺(jué)不到這是一個(gè)復(fù)雜的 class.比如Unix 的文件讀寫接口
2. Program to an interface, not an implementation
- 面向接口編程,而不是實(shí)現(xiàn)
- 工廠模式伍派、策略模式等等
3. 高內(nèi)聚低耦合
- 將模塊間的耦合降到最低江耀,努力讓一個(gè)模塊做到精益求精.內(nèi)聚意味著獨(dú)立和重用剩胁,耦合意味著多米諾骨牌效應(yīng).
4. 迪米特法則
又稱最少知識(shí)原則诉植,該原則告訴我們要降低耦合。
定義:一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象保持最少的了解
問(wèn)題:類與類之間的關(guān)系越密切昵观,耦合度越大晾腔,當(dāng)一個(gè)類發(fā)生改變時(shí),對(duì)另一個(gè)類的影響也越大啊犬。
解決:盡量降低類與類之間的耦合
-
對(duì)于對(duì)象 ‘O’ 中一個(gè)方法’M’灼擂,M 應(yīng)該只能夠訪問(wèn)以下對(duì)象中的方法:
- 對(duì)象O本身
- 參數(shù)對(duì)象
- 與對(duì)象O直接相關(guān)的對(duì)象
- 方法中創(chuàng)建或?qū)嵗膶?duì)象
應(yīng)用模式:外觀模式,提供一個(gè)統(tǒng)一的接口來(lái)訪問(wèn)子系統(tǒng)中的一群接口觉至。外觀定義了一個(gè)高層接口剔应,讓系統(tǒng)更容易使用
5. 好萊塢原則
- 你不要找我,我會(huì)找你 。 高層組件對(duì)待底層組件的方式:別調(diào)用我們峻贮,我們會(huì)調(diào)用你
- 應(yīng)用場(chǎng)景:
- 觀察者模式,以通知替代輪詢
- 工廠模式
- Ioc依賴注入,DI控制反轉(zhuǎn)設(shè)計(jì)的基礎(chǔ)席怪,所有組件都是被動(dòng)的,初始化和調(diào)用都由容器負(fù)責(zé)
- 模板模式
6. 無(wú)環(huán)依賴原則
- 包纤控、服務(wù)之間的依賴結(jié)構(gòu)必須是一個(gè)直接的無(wú)環(huán)圖形挂捻,不能出現(xiàn)循環(huán)依賴
- 打破循環(huán)依賴關(guān)系,解決關(guān)系耦合問(wèn)題:
- 使用依賴倒置原則和接口隔離原則
- 創(chuàng)建新的包船万,將共同類抽象出來(lái)放在新的包里
7. 減少拋異常
- 除了那些必須告訴用戶的錯(cuò)誤刻撒,其他錯(cuò)誤盡量在軟件內(nèi)部處理掉,不要拋出
總結(jié)
- 如何去遵守這些原則耿导。對(duì)這些原則的遵守并不是是和否的問(wèn)題声怔,而是多和少的問(wèn)題,也就是說(shuō)舱呻,我們一般不會(huì)說(shuō)有沒(méi)有遵守捧搞,而是說(shuō)遵守程度的多少。任何事都是過(guò)猶不及狮荔,設(shè)計(jì)模式的六個(gè)設(shè)計(jì)原則也是一樣胎撇,制定這六個(gè)原則的目的并不是要我們刻板的遵守他們,而需要根據(jù)實(shí)際情況靈活運(yùn)用殖氏。對(duì)他們的遵守程度只要在一個(gè)合理的范圍內(nèi)晚树,就算是良好的設(shè)計(jì)。