面向对象的原则里氏替换原则
里氏替换原则
里氏替换原则的英文全称是Liskov Substitution Principle,缩写是LSP。
LSP第一种定义是:如果对每一个类型的为S的对象O1,都有类型为T的对象O2,使得以T定义的所有程序P在所有对象O1都替换成O2时,程序P的行为不会发生变化,那么类型S是类型T的子类型。这不太好理解。
LSP的第二种定义是:所有引用基类的地方必须能透明地使用其子类的对象。通俗的讲就是:只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或者异常,使用者可能根本就不需要知道是父类还是子类,但是,反过来就不行了,有子类出现的地方,父类未必就能适应。因为子类有的东西父类不一定有。
说了这么多,最终总结就两个字:抽象。为了便于理解这个原则,我们写了一个简单示例来描述,先看UML图:
里氏替换原则.png再来看看具体的代码:
/**
* 窗口类
*/
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源码设计模式》 何红辉、关爱民著