摘要:什么是里氏替換原則某個(gè)對(duì)象實(shí)例的子類(lèi)實(shí)例應(yīng)當(dāng)可以在不影響程序正確性的基礎(chǔ)上替換它們。除了在編程語(yǔ)言層面,在前端實(shí)際工作中,你可能會(huì)聽(tīng)到一個(gè)叫作的概念,這個(gè)概念我認(rèn)為也是里氏替換原則的一直延伸。
這是理解SOLID原則,關(guān)于里氏替換原則為什么提倡我們面向抽象層編程而不是具體實(shí)現(xiàn)層,以及為什么這樣可以使代碼更具維護(hù)性和復(fù)用性。什么是里氏替換原則
Objects should be replaceable with instances of their subtypes without altering the correctness of that program.某個(gè)對(duì)象實(shí)例的子類(lèi)實(shí)例應(yīng)當(dāng)可以在不影響程序正確性的基礎(chǔ)上替換它們。
這句話的意思是說(shuō),當(dāng)我們?cè)趥鬟f一個(gè)父抽象的子類(lèi)型時(shí),你需要保證你不會(huì)修改任何關(guān)于這個(gè)父抽象的行為和狀態(tài)語(yǔ)義。
如果你不遵循里氏替換原則,那么你可能會(huì)面臨以下問(wèn)題:
類(lèi)繼承會(huì)變得很混亂,因此奇怪的行為會(huì)發(fā)生
對(duì)于父類(lèi)的單元測(cè)試對(duì)于子類(lèi)是無(wú)效的,因此會(huì)降低代碼的可測(cè)試性和驗(yàn)證程度
通常打破這條原則的情況發(fā)生在修改父類(lèi)中在其他方法中使用的,與當(dāng)前子類(lèi)無(wú)關(guān)聯(lián)的內(nèi)部或者私有變量。這通常算得上是一種對(duì)于類(lèi)本身的一次潛在攻擊,而且這種攻擊可能是你在不經(jīng)意間自己發(fā)起的,而且不僅在子類(lèi)中。
反面例子讓我們通過(guò)一個(gè)反面例子來(lái)演示這種修改行為和它所產(chǎn)生的后果。比如,我們有一個(gè)關(guān)于Store的抽象類(lèi)和它的實(shí)現(xiàn)類(lèi)BasicStore,這個(gè)類(lèi)會(huì)儲(chǔ)存一些消息在內(nèi)存中,直到儲(chǔ)存的個(gè)數(shù)超過(guò)每個(gè)上限。客戶(hù)端代碼的實(shí)現(xiàn)也很簡(jiǎn)單明了,它期望通過(guò)調(diào)用retrieveMessages就可以獲取到所有儲(chǔ)存的消息。
代碼如下:
interface Store { store(message: string); retrieveMessages(): string[]; } const STORE_LIMIT = 5; class BasicStore implements Store { protected stash: string[] = []; protected storeLimit: number = STORE_LIMIT; store(message: string) { if (this.storeLimit === this.stash.length) { this.makeMoreRoomForStore(); } this.stash.push(message); } retrieveMessages(): string[] { return this.stash; } makeMoreRoomForStore(): void { this.storeLimit += 5; } }
之后通過(guò)繼承BasicStore,我們又創(chuàng)建了一個(gè)新的RotatingStore實(shí)現(xiàn)類(lèi),如下:
class RotatingStore extends BasicStore { makeMoreRoomForStore() { this.stash = this.stash.slice(1); } }
注意RotatingStore中覆蓋父類(lèi)makeMoreRoomForStore方法的代碼以及它是如何隱蔽地改變了父類(lèi)BasicStore關(guān)于stash的狀態(tài)語(yǔ)義的。它不僅修改了stash變量,還銷(xiāo)毀了在程序進(jìn)程中已儲(chǔ)存的消息已為將來(lái)的消息提供額外的空間。
在使用RotatingStore的過(guò)程中,我們會(huì)遇到一些奇怪的現(xiàn)象,這正式由于RotatingStore本身產(chǎn)生的,如下:
const st: Store = new RotatingStore() st.store("hello") st.store("world") st.store("how") st.store("are") st.store("you") st.store("today") st.store("sir?") st.retrieveMessages() // 一些消息丟失了
一些消息會(huì)無(wú)故消失,當(dāng)前這個(gè)類(lèi)的表現(xiàn)邏輯與所有消息均可以被取出的基本需求不一致。
如何實(shí)踐里氏替換原則為了避免這種奇怪現(xiàn)象的發(fā)生,里氏替換原則推薦我們通過(guò)在子類(lèi)中調(diào)用父類(lèi)的公有方法來(lái)獲取一些內(nèi)部狀態(tài)變量,而不是直接使用它。這樣我們就可以保證父類(lèi)抽象中正確的狀態(tài)語(yǔ)義,從而避免了副作用和非法的狀態(tài)轉(zhuǎn)變。
它也推薦我們應(yīng)當(dāng)盡可能的使基本抽象保持簡(jiǎn)單和最小化,因?yàn)閷?duì)于子類(lèi)來(lái)說(shuō),有助于提供父類(lèi)的擴(kuò)展性。如果一個(gè)父類(lèi)是比較復(fù)雜的,那么子類(lèi)在覆蓋它的時(shí)候,在不影響父類(lèi)狀態(tài)語(yǔ)義的情況下進(jìn)行擴(kuò)展絕非易事。
對(duì)于內(nèi)部系統(tǒng)做可行的后置條件檢查也是一個(gè)不錯(cuò)的方式,這種檢查通常會(huì)驗(yàn)證是否子類(lèi)會(huì)攪亂一些關(guān)鍵代碼的運(yùn)行路徑(譯者注:也可以理解為狀態(tài)語(yǔ)義),但是我本身對(duì)這個(gè)實(shí)踐并沒(méi)有太多的經(jīng)驗(yàn),所以無(wú)法給予具體的例子。
代碼評(píng)論也可以一定程度上給予好的幫助。當(dāng)你在開(kāi)發(fā)一些你可能無(wú)意間做出一些對(duì)已有系統(tǒng)的破壞,但是你的同事可能會(huì)很容易地發(fā)現(xiàn)這些(當(dāng)局者迷旁觀者清)。軟件設(shè)計(jì)保持一致性是一件十分重要的事情,因此應(yīng)當(dāng)盡早、盡可能多地查明那些對(duì)對(duì)象繼承鏈作出潛在修改的代碼。
最后,在單一職責(zé)原則中,我們?cè)峒?,考慮使用組合模式來(lái)替換繼承模式。
總結(jié)正如你所看到的,在開(kāi)發(fā)軟件時(shí),我們往往需要額外花一些努力和精力來(lái)使它變得更好。將這些原則牢記于心,理解它們所存在的意義以及它們想要解決的問(wèn)題,這樣會(huì)使你的工作變得更加容易、更具條理性,但是同時(shí)記住,這并不是一件容易的事,相反,你應(yīng)當(dāng)在構(gòu)思軟件時(shí),花相當(dāng)多的事件思考如何更好地實(shí)踐這些原則。
試著讓自己設(shè)計(jì)的軟件系統(tǒng)具備可適應(yīng)性,這種適應(yīng)性可以抵御各種不利的變化以及潛在的錯(cuò)誤,這樣自然而然地可以使你少加班和早回家(譯者注:看來(lái)加班是每個(gè)程序員都要面臨的問(wèn)題?。?/p> 譯者注
這是SOLID原則中我所接觸和了解較少的一個(gè)原則,但經(jīng)過(guò)仔細(xì)思考后,發(fā)現(xiàn)其實(shí)我們還是經(jīng)常會(huì)在實(shí)際工作中運(yùn)用它的。
在許多面向相對(duì)的編程語(yǔ)言中,關(guān)于對(duì)象的繼承機(jī)制中,都會(huì)提供一些內(nèi)部變量和狀態(tài)的修飾符,比如public(公有)、protect(保護(hù))和private(私有),關(guān)于這些修飾符本身的異同這里不再贅述,我想說(shuō)的是,這些修飾符存在必然有它存在的意義,一定要在實(shí)際工作中,使用它們。之前做java后端時(shí),經(jīng)常在公司的項(xiàng)目的歷史代碼中發(fā)現(xiàn),很少使用protect和private對(duì)類(lèi)內(nèi)部的方法和變量做約束,可見(jiàn)當(dāng)時(shí)的編寫(xiě)者并沒(méi)有對(duì)類(lèi)本身的職能有一個(gè)清晰的認(rèn)識(shí),又或者是隨著時(shí)間一步步迭代出來(lái)的結(jié)果。
那么問(wèn)題來(lái)了,一些靜態(tài)語(yǔ)言有這些修飾符,但是像javascript這種鴨子類(lèi)型語(yǔ)言怎么辦呢?其實(shí)沒(méi)有必要擔(dān)心,最早開(kāi)始學(xué)前端的時(shí)候,這個(gè)問(wèn)題我就問(wèn)過(guò)自己無(wú)數(shù)次,javascript雖然沒(méi)有這些修飾符,但是我們可以通過(guò)別的方式來(lái)達(dá)到類(lèi)似的效果,或者使用typescript。
除了在編程語(yǔ)言層面,在前端實(shí)際工作中,你可能會(huì)聽(tīng)到一個(gè)叫作immutable的概念,這個(gè)概念我認(rèn)為也是里氏替換原則的一直延伸。因?yàn)楫?dāng)前的前端框架一般提倡的理念均是f(state) => view,即數(shù)據(jù)狀態(tài)代表視圖,而數(shù)據(jù)狀態(tài)本身由于javascript動(dòng)態(tài)語(yǔ)言的特性,很容易會(huì)在不經(jīng)意間被修改,一旦存在這種修改,視圖中便會(huì)產(chǎn)生一些意想不到的問(wèn)題,因此immutable和函數(shù)式的概念才會(huì)在前段時(shí)間火起來(lái)。
寫(xiě)在最后經(jīng)過(guò)這五篇文章,我們來(lái)分別總結(jié)一下這五條基本原則以及它們帶來(lái)的好處:
單一職責(zé)原則:提高代碼實(shí)現(xiàn)層的內(nèi)聚度,降低實(shí)現(xiàn)單元彼此之間的耦合度
開(kāi)閉原則:提高代碼實(shí)現(xiàn)層的可擴(kuò)展性,提高面臨改變的可適應(yīng)性,降低修改代碼的冗余度
里氏替換原則:提高代碼抽象層的可維護(hù)性,提高實(shí)現(xiàn)層代碼與抽象層的一致性
接口隔離原則:提高代碼抽象層的內(nèi)聚度,降低代碼實(shí)現(xiàn)層與抽象層的耦合度,降低代碼實(shí)現(xiàn)層的冗余度
依賴(lài)倒置原則:降低代碼實(shí)現(xiàn)層由依賴(lài)關(guān)系產(chǎn)生的耦合度,提高代碼實(shí)現(xiàn)層的可測(cè)試性
可以注意到我這里刻意使用了降低/提高 + 實(shí)現(xiàn)層/抽象層 + 特性/程度(耦合度、內(nèi)聚度、擴(kuò)展性、冗余度、可維護(hù)性,可測(cè)試性)這樣的句式,之所以這么做是因?yàn)樵谲浖ぷ髦?,我們理想中的軟件?yīng)當(dāng)具備的特點(diǎn)是, 高內(nèi)聚、低耦合、可擴(kuò)展、少冗余、可維護(hù)、易于測(cè)試,而這五個(gè)原則也按正確的方向,將我們的軟件系統(tǒng)向我們理想中的標(biāo)準(zhǔn)推進(jìn)。
為了便于對(duì)比,特別繪制了下面的表格,希望大家從真正意義上做到將這些原則牢記于心,并付諸于行。
原則 | 耦合度 | 內(nèi)聚度 | 擴(kuò)展性 | 冗余度 | 維護(hù)性 | 測(cè)試性 | 適應(yīng)性 | 一致性 |
---|---|---|---|---|---|---|---|---|
單一職責(zé)原則 | - | + | o | o | + | + | o | o |
開(kāi)閉原則 | o | o | + | - | + | o | + | o |
里氏替換原則 | - | o | o | o | + | o | o | + |
接口隔離原則 | - | + | o | - | o | o | + | o |
依賴(lài)倒置原則 | - | o | o | - | o | + | + | o |
Note: +代表增加, -代表降低, o代表持平
關(guān)注公眾號(hào) 全棧101,只談技術(shù),不談人生
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/107361.html
這是理解SOLID原則中,關(guān)于依賴(lài)倒置原則如何幫助我們編寫(xiě)低耦合和可測(cè)試代碼的第一篇文章。 寫(xiě)在前頭 當(dāng)我們?cè)谧x書(shū),或者在和一些別的開(kāi)發(fā)者聊天的時(shí)候,可能會(huì)談及或者聽(tīng)到術(shù)語(yǔ)SOILD。在這些討論中,一些人會(huì)提及它的重要性,以及一個(gè)理想中的系統(tǒng),應(yīng)當(dāng)包含它所包含的5條原則的特性。 我們?cè)诿看蔚墓ぷ髦校憧赡軟](méi)有那么多時(shí)間思考關(guān)于架構(gòu)這個(gè)比較大的概念,或者在有限的時(shí)間內(nèi)或督促下,你也沒(méi)有辦法實(shí)踐一些好...
摘要:前言本章我們要講解的是五大原則語(yǔ)言實(shí)現(xiàn)的第篇,里氏替換原則。因此,違反了里氏替換原則。與行為有關(guān),而不是繼承到現(xiàn)在,我們討論了和繼承上下文在內(nèi)的里氏替換原則,指示出的面向?qū)ο蟆? 前言 本章我們要講解的是S.O.L.I.D五大原則JavaScript語(yǔ)言實(shí)現(xiàn)的第3篇,里氏替換原則LSP(The Liskov Substitution Principle )。英文原文:http://fre...
摘要:設(shè)計(jì)原則梳理,參考核心技術(shù)與最佳實(shí)踐敏捷開(kāi)發(fā)原則模式與實(shí)踐,文章面向?qū)ο笤O(shè)計(jì)的五大原則設(shè)計(jì)模式原則單一職責(zé)原則定義特性?xún)H有一個(gè)引起類(lèi)變化的原因一個(gè)類(lèi)只承擔(dān)一項(xiàng)職責(zé)職責(zé)變化的原因避免相同的職責(zé)分散到不同的類(lèi),功能重復(fù)問(wèn)題一個(gè)類(lèi)承擔(dān)的職責(zé)過(guò)多, PHP設(shè)計(jì)原則梳理,參考《PHP核心技術(shù)與最佳實(shí)踐》、《敏捷開(kāi)發(fā)原則、模式與實(shí)踐》,文章PHP面向?qū)ο笤O(shè)計(jì)的五大原則、設(shè)計(jì)模式原則SOLID 單一...
摘要:在開(kāi)發(fā)設(shè)計(jì)中有一些常用原則或者潛規(guī)則,根據(jù)筆者的經(jīng)驗(yàn),這里稍微總結(jié)一下最最常用的,以饗讀者。是處理復(fù)雜性的一個(gè)原則。參考六大設(shè)計(jì)原則里氏替換原則奧卡姆剃刀如有問(wèn)題可以通過(guò)郵件微信聯(lián)系我。 在開(kāi)發(fā)設(shè)計(jì)中有一些常用原則或者潛規(guī)則,根據(jù)筆者的經(jīng)驗(yàn),這里稍微總結(jié)一下最最常用的,以饗讀者。 DRY 這里的DRY是Do Not Repeat Yourself的縮寫(xiě)。具體解釋參見(jiàn) ,嚴(yán)謹(jǐn)?shù)亩x是 E...
閱讀 1187·2021-11-24 10:21
閱讀 2629·2021-11-19 11:35
閱讀 1727·2019-08-30 15:55
閱讀 1360·2019-08-30 15:54
閱讀 1260·2019-08-30 15:53
閱讀 3567·2019-08-29 17:21
閱讀 3366·2019-08-29 16:12
閱讀 3480·2019-08-29 15:23