里氏替换原则(Liskov Substitution Principle ,缩写:LSP),原则说任何基类可以出现的地方,子类一定可以出现。LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能在基类的基础上增加新的行为。
二、核心原理
里氏替换原则的核心原理是抽象,抽象又依赖于继承这个特性,在OOP中,继承的优缺点相当明显,主要有以下几点
优点:1.代码重用,减少创建类的成本,每个子类都拥有父类的方法和属性;2.子类与父类基本相似,但又与父类有所区别;3.提高代码的可扩展性。
缺点:1.继承是侵入性的,只要继承就必须拥有父类的所有属性和方法;2.可能造成子类代码冗余、灵活性降低,因为子类必须拥有父类的属性和方法。
三、实现方法
下面引用《Android源码设计模式解析与实战》一书中简单示例:
//窗口类
public class Window{
public void show(View child){
child.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() {
//绘制按钮
}
}
//TextView的具体实现
public class TextView extends View{
@Override
public void draw() {
//绘制文本
}
}
上述示例中,Window依赖于View,而View定义了一个视图抽象,measure是各个子类共享的方法,子类通过复写View的draw方法实现具有各自特色的功能,在这里,这个功能就是绘制自身的内容。任何继承自View类的子类都可以设置给show方法,也就是所说的里氏替换。通过里氏替换,就可以自定义各式各样、千变万化的View,然后传递给Window,Window负责组织View,并且将View显示在屏幕上。
上一篇博客 “面向对象六大原则(二):开闭原则” 中的示例也很好地反应了里氏替换原则,即MemoryCache、DiskCache、DoubleCache都可以替换ImageCache的工作,并且能够保证行为的正确性。ImageCache建立了获取缓存图片、保存缓存图片的接口规范,MemoryCache等根据接口规范实现了相应的功能,用户只需要在使用时指定具体的缓存对象就可以动态地替换ImageLoader中的缓存策略。这就使得ImageLoader的缓存系统具有了无限的可能性,也就是保证了可扩展性。
四、分析
里氏替换原则就是建立抽象,通过抽象建立规范,具体的实现在运行时替换掉抽象,保证系统的扩展性、灵活性。
开闭原则与里氏原则往往是生死相依、不离不弃,通过里氏替换来达到对扩展开放,对修改关闭的效果。然而,这两个原则都同时强调了一个OOP的重要特性——抽象,因此,在开发过程中运用抽象是走向代码优化的重要一步。