面向對象編程的六大原則
讓程序有更好的拓展性--里氏替換原則
里氏替換原則的英文全稱是Liskov Substiution Principle。縮寫是LSP。
LSP的第一種定義是:如果對沒一個類型為S的對象O1,都有類型為T的對象O2,使得以T定義的所有程序P在所有的對象O1都代換成O2時,程序P的行為沒有發生變化,那么類型S是類型T的子類型。
上面的描述是不是看過去就頭大?是的,就像后悔的作者一樣,頭大!那么我們來看看第二種定義。
LSP的第二種定義是:所有引用基類的地方必須能透明地使用其子類的對象。
正如我們知道的,面向對象的三大特性是:繼承、封裝、多態,里氏替換原則就是依賴于繼承和多態兩大特性。簡單地說,就是父類能出現的地方子類就可以出現,而且替換成子類也不會出現任何錯誤或者異常,而使用者也無需知道是父類還是子類。但是有子類的地方不一定適用于所有父類。其實總結就兩個字抽象。
此處我們使用Android中Window與View的關系進行示例:
public class Window {
public void showView(View child) {
child.draw();
}
}
public abstract class View {
public abstract void draw();
public void measure(int width, int height) {
// 調整視圖大小
}
}
public class Button extends View {
@Override
public void draw() {
// 繪制按鈕
}
}
public class TextView extends View {
@Override
public void draw() {
// 繪制文本
}
}
在上述實例中,Window依賴于View,而View定義了一個視圖抽象,measure是各個子類所共享的方法,子類通過覆寫draw方法實現各具特色的功能。所有繼承自View類的子類都可以設置給show方法,這就是所謂的里氏替換原則。
通過里氏替換原則就可以自定義各式各樣千變萬化的View,然后傳輸給Window,Window負責將View展示到屏幕上。
里氏替換原則的和心愿禮是抽象,抽象又依賴于繼承這個特性,在OOP中,繼承的優缺點都很明顯:
優點:
- 提高代碼的可拓展性。
- 子類和父類基本相似,但是保留自我特性。
- 代碼復用,減少類的數量和代碼量,每個子類都可以使用父類的方法和屬性。
缺點: - 繼承就必須擁有父類的方法和屬性。
- 可能造成子類的代碼冗余,因為它必須擁有父類的屬性和方法,可能會降低子類的靈活性。
此時我們回過頭看上一篇文章開閉原則中的ImageLoader類中的setImageCache方法,即很好地反映了里氏替換原則。
//設置為磁盤緩存
imageLoader.setmImageCache(new DiskCache());
//設置為內存緩存
imageLoader.setmImageCache(new MemoryCache());
//設置為雙緩存
imageLoader.setmImageCache(new DoubleCache());
//設置為自定義緩存
imageLoader.setmImageCache(new ImageCache() {
@Override
public Bitmap get(String url) {
return null;
}
@Override
public void put(String url, Bitmap bitmap) {
}
});
在上述代碼中,我們可以使用任何一個實現了ImageCache接口的類去實現緩存功能,同時也可以直接實現ImageCache接口去自定義我們的緩存。
想象一下,如果此處我們不能這樣,只能通過不同的方法去設置我們的緩存方式,此時便會增加相當多的代碼量,并且影響到代碼的簡潔和穩定性和拓展性。比如上一章中的setDiskCacheEnable方法,這種方式明顯是不可取的。
里氏替換原則就為這類問題提供了指導,也就是建立抽象,通過抽象來建立規范,具體實現在運行時取代抽象,保證了系統的拓展性和靈活性。開閉原則和里氏替換原則一般情況下是生死相依不離不棄的,通過里氏替換原則打到對拓展開放,對修改關閉的效果。
本章和上章內容其實都搶掉了一個OOP的重要特性,抽象。在開發過程中運用好抽象,是走向代碼優化的第一步!