天天看點

《Android 源碼設計模式解析與實戰》——第1章,第1.3節建構擴充性更好的系統——裡氏替換原則

本節書摘來自異步社群《android 源碼設計模式解析與實戰》一書中的第1章,第1.3節建構擴充性更好的系統——裡氏替換原則,作者 何紅輝 , 關愛民,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

1.3 建構擴充性更好的系統——裡氏替換原則

裡氏替換原則英文全稱是liskov substitution principle,縮寫是lsp。lsp的第一種定義是:如果對每一個類型為s的對象o1,都有類型為t的對象o2,使得以t定義的所有程式p在所有的對象o1都代換成o2時,程式p的行為沒有發生變化,那麼類型s是類型t的子類型。上面這種描述确實不太好了解,我們再看看另一個直截了當的定義。裡氏替換原則第二種定義:所有引用基類的地方必須能透明地使用其子類的對象。

我們知道,面向對象的語言的三大特點是繼承、封裝、多态,裡氏替換原則就是依賴于繼承、多态這兩大特性。裡氏替換原則簡單來說就是,所有引用基類的地方必須能透明地使用其子類的對象。通俗點講,隻要父類能出現的地方子類就可以出現,而且替換為子類也不會産生任何錯誤或異常,使用者可能根本就不需要知道是父類還是子類。但是,反過來就不行了,有子類出現的地方,父類未必就能适應。說了那麼多,其實最終總結就兩個字:抽象。

小民為了深入地了解android中的window與view的關系,特意寫了一個簡單示例,為了便于了解,我們先看如圖1-3所示。

《Android 源碼設計模式解析與實戰》——第1章,第1.3節建構擴充性更好的系統——裡氏替換原則

我們看看具體的代碼實作:

上述示例中,window依賴于view,而view定義了一個視圖抽象,measure是各個子類共享的方法,子類通過覆寫view的draw方法實作具有各自特色的功能,在這裡,這個功能就是繪制自身的内容。任何繼承自view類的子類都可以設定給show方法,就是所說的裡氏替換。通過裡氏替換,就可以自定義各式各樣、千變萬化的view,然後傳遞給window,window負責組織view,并且将view顯示到螢幕上。

裡氏替換原則的核心原理是抽象,抽象又依賴于繼承這個特性,在oop當中,繼承的優缺點都相當明顯。優點有以下幾點:

(1)代碼重用,減少建立類的成本,每個子類都擁有父類的方法和屬性;

(2)子類與父類基本相似,但又與父類有所差別;

(3)提高代碼的可擴充性。

繼承的缺點:

(1)繼承是侵入性的,隻要繼承就必須擁有父類的所有屬性和方法;

(2)可能造成子類代碼備援、靈活性降低,因為子類必須擁有父類的屬性和方法。

事物總是具有兩面性,如何權衡利與弊都是需要根據具體情況來做出選擇并加以處理。裡氏替換原則指導我們建構擴充性更好的軟體系統,我們還是接着上面的imageloader來做說明。

圖1-2也很好地反應了裡氏替換原則,即memorycache、diskcache、doublecache都可以替換imagecache的工作,并且能夠保證行為的正确性。imagecache建立了擷取緩存圖檔、儲存緩存圖檔的接口規範,memorycache等根據接口規範實作了相應的功能,使用者隻需要在使用時指定具體的緩存對象就可以動态地替換imageloader中的緩存政策。這就使得imageloader的緩存系統具有了無限的可能性,也就是保證了可擴充性。

想象一種情況,當imageloader中的setimagecache(imagecache cache)中的cache對象不能夠被子類所替換,那麼使用者如何設定不同的緩存對象,以及使用者如何自定義自己的緩存實作,通過1.3節中的usediskcache方法嗎?顯然不是的,裡氏替換原則就為這類問題提供了指導原則,也就是建立抽象,通過抽象建立規範,具體的實作在運作時替換掉抽象,保證系統的擴充性、靈活性。開閉原則和裡氏替換原則往往是生死相依、不棄不離的,通過裡氏替換來達到對擴充開放,對修改關閉的效果。然而,這兩個原則都同時強調了一個oop的重要特性——抽象,是以,在開發過程中運用抽象是走向代碼優化的重要一步。

繼續閱讀