里氏替換原則
里氏替換原則的英文全稱是Liskov Substitution Principle,縮寫是LSP。
LSP第一種定義是:如果對每一個類型的為S的對象O1,都有類型為T的對象O2,使得以T定義的所有程序P在所有對象O1都替換成O2時,程序P的行為不會發生變化,那么類型S是類型T的子類型。這不太好理解。
LSP的第二種定義是:所有引用基類的地方必須能透明地使用其子類的對象。通俗的講就是:只要父類能出現的地方子類就可以出現,而且替換為子類也不會產生任何錯誤或者異常,使用者可能根本就不需要知道是父類還是子類,但是,反過來就不行了,有子類出現的地方,父類未必就能適應。因為子類有的東西父類不一定有。
說了這么多,最終總結就兩個字:抽象。為了便于理解這個原則,我們寫了一個簡單示例來描述,先看UML圖:
再來看看具體的代碼:
/**
* 窗口類
*/
public class Window {
public show(View chile) {
chile.draw();
}
}
/**
* 建立視圖抽象,測量視圖的寬高為公用代碼,繪制實現交給具體的子類
*/
public abstract class View {
public abstract void draw();
public void measure(int width,int height) {
}
}
/**
* Button的具體實現
*/
public class Button extends View {
@Override
public void draw() {
//繪制Button
}
}
/**
* TextView的具體實現
*/
public class TextView extends View {
@Override
public void draw() {
//繪制TextV
}
}
上述示例中,Window依賴于View,而View定義了一個視圖抽象,measure是各個子類共享的方法,子類通過覆寫View的draw方法實現各自的功能。任何繼承自View的子類都可以設置給Window的show方法,這就是里氏替換原則。通過里氏替換,就可以自定義各自各樣的view,然后傳遞個Window,Window負責組織view,并且將View顯示到屏幕上。
里氏替換原則的核心是抽象,抽象又依賴于繼承這個特性,在OOP當中,繼承的優缺點都相當的明顯。優點為:
- 代碼重用,減少創建的成本,每個子類擁有父類的方法和屬性
- 子類和父類基本相似,但又與父類有所區別
- 提高代碼的可擴展性,實現父類的方法就可以了,很多開源框架的擴展接口都是通過繼承父類完成的。
- 提高產品或項目的開放性。
繼承的缺點:
- 繼承是侵入性的,只要繼承就必須擁有父類的所有屬性和方法
- 可能造成子類代碼冗余、靈活性降低,因為子類必須擁有父類的屬性和方法
- 增強了耦合性。當父類的常量、變量和方法被修改時,必須考慮子類的修改,而且在缺乏規范的環境下,這種修好可能帶來非常糟糕的結果---大片的代碼需要重構。
結束
開閉原則中的示例圖片緩存系統也很好地反應了里氏替換原則,即MemoryCache、DiskCache、DoubleCache都可以替換成ImageCache的工作,并保證行為的正確性。實際中,開閉原則和里氏替換原則往往是相互關聯的,通過里氏替換來達到擴展開放、修改關閉的效果。然而,這兩個原則都強調一個OOP的重要特性--抽象。
參考書籍
《Android源碼設計模式》 何紅輝、關愛民著