Liskov于1987年提出了一個關(guān)于繼承的原則“Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.”——“繼承必須確保超類所擁有的性質(zhì)在子類中仍然成立。”也就是說,當(dāng)一個子類的實例應(yīng)該能夠替換任何其超類的實例時,它們之間才具有is-A關(guān)系。
該原則稱為Liskov Substitution Principle——里氏替換原則。
我們來研究一下LSP的實質(zhì)。學(xué)習(xí)OO的時候,我們知道,一個對象是一組狀態(tài)和一系列行為的組合體。狀態(tài)是對象的內(nèi)在特性,行為是對象的外在特性。LSP所表述的就是在同一個繼承體系中的對象應(yīng)該有共同的行為特征。
這一點上,表明了OO的繼承與日常生活中的繼承的本質(zhì)區(qū)別。舉一個例子:生物學(xué)的分類體系中把企鵝歸屬為鳥類。我們模仿這個體系,設(shè)計出這樣的類和關(guān)系。
類“鳥”中有個方法fly,企鵝自然也繼承了這個方法,可是企鵝不能飛阿,于是,我們在企鵝的類中覆蓋了fly方法,告訴方法的調(diào)用者:企鵝是不會飛的。這完全符合常理。但是,這違反了LSP,企鵝是鳥的子類,可是企鵝卻不能飛!需要注意的是,此處的“鳥”已經(jīng)不再是生物學(xué)中的鳥了,它是軟件中的一個類、一個抽象。
有人會說,企鵝不能飛很正常啊,而且這樣編寫代碼也能正常編譯,只要在使用這個類的客戶代碼中加一句判斷就行了。但是,這就是問題所在!首先,客戶代碼和“企鵝”的代碼很有可能不是同時設(shè)計的,在當(dāng)今軟件外包一層又一層的開發(fā)模式下,你甚至根本不知道兩個模塊的原產(chǎn)地是哪里,也就談不上去修改客戶代碼了??蛻舫绦蚝芸赡苁沁z留系統(tǒng)的一部分,很可能已經(jīng)不再維護(hù),如果因為設(shè)計出這么一個“企鵝”而導(dǎo)致必須修改客戶代碼,誰應(yīng)該承擔(dān)這部分責(zé)任呢?(大概是上帝吧,誰叫他讓“企鵝”不能飛的。_)“修改客戶代碼”直接違反了OCP,這就是OCP的重要性。違反LSP將使既有的設(shè)計不能封閉!
修正后的設(shè)計如下:
LSP并沒有提供解決這個問題的方案,而只是提出了這么一個問題。 于是,工程師們開始關(guān)注如何確保對象的行為。1988年,B. Meyer提出了Design by Contract(契約式設(shè)計)理論。DbC從形式化方法中借鑒了一套確保對象行為和自身狀態(tài)的方法,其基本概念很簡單:
每個方法調(diào)用之前,該方法應(yīng)該校驗傳入?yún)?shù)的正確性,只有正確才能執(zhí)行該方法,否則認(rèn)為調(diào)用方違反契約,不予執(zhí)行。這稱為前置條件(Pre-condition)。
一旦通過前置條件的校驗,方法必須執(zhí)行,并且必須確保執(zhí)行結(jié)果符合契約,這稱之為后置條件(Post-condition)。
對象本身有一套對自身狀態(tài)進(jìn)行校驗的檢查條件,以確保該對象的本質(zhì)不發(fā)生改變,這稱之為不變式(Invariant)。
以上是單個對象的約束條件。為了滿足LSP,當(dāng)存在繼承關(guān)系時,子類中方法的前置條件必須與超類中被覆蓋的方法的前置條件相同或者更寬松;而子類中方法的后置條件必須與超類中被覆蓋的方法的后置條件相同或者更為嚴(yán)格。
DIP 依賴倒置原則
依賴倒置(Dependence Inversion Principle)原則講的是:要依賴于抽象,不要依賴于具體。
簡單的說,依賴倒置原則要求客戶端依賴于抽象耦合。原則表述:
抽象不應(yīng)當(dāng)依賴于細(xì)節(jié);細(xì)節(jié)應(yīng)當(dāng)依賴于抽象;
要針對接口編程,不針對實現(xiàn)編程。
ISP 接口隔離原則
使用多個專門的接口比使用單一的總接口要好。廣義的接口:一個接口相當(dāng)于劇本中的一種角色,而此角色在一個舞臺上由哪一個演員來演則相當(dāng)于接口的實現(xiàn)。因此一個接口應(yīng)當(dāng)簡單的代表一個角色,而不是一個角色。,如果系統(tǒng)設(shè)計多個角色的話,則應(yīng)當(dāng)每一個角色都由一個特定的接口代表。狹義的接口(Interface):接口隔離原則講的就是同一個角色提供寬、窄不同的接口,以對付不同的客戶端。