上一讲,我们讲了开放封闭原则,想要让系统符合开放封闭原则,最重要的就是我们要构建起相应的扩展模型,所以,我们要面向接口编程。
而大部分的面向接口编程要依赖于继承实现,虽然我们在前面的课程中说过,继承的重要性不如封装和多态,但在大部分面向对象程序设计语言中,继承却是构建一个对象体系的重要组成部分。
理论上,在定义了接口之后,我们就可以把继承这个接口的类完美地嵌入到我们设计好的体系之中。然而,用了继承,子类就一定设计对了吗?事情可能并没有这么简单。
新的类虽然在语法上声明了一个接口,形成了一个继承关系,但我们要想让这个子类真正地扮演起这个接口的角色,还需要有一个好的继承指导原则。
所以,这一讲,我们就来看看可以把继承体系设计好的设计原则:Liskov 替换法则。
Liskov 替换原则
2008 年,图灵奖授予 Barbara Liskov,表彰她在程序设计语言和系统设计方法方面的卓越工作。她在设计领域影响最深远的就是以她名字命名的 Liskov 替换原则(Liskov substitution principle,简称 LSP)。
1988 年,Barbara Liskov 在描述如何定义子类型时写下这样一段话:
这里需要如下替换性质:若每个类型 S 的对象 o1,都存在一个类型 T 的对象 o2,使得在所有针对 T 编程的程序 P 中,用 o1 替换 o2 后,程序 P 行为保持不变,则 S 是 T 的子类型。
用通俗的讲法来说,意思就是,子类型(subtype)必须能够替换其父类型(base type)。
这句话看似简单,但是违反这个原则,后果是很严重的,比如,父类型规定接口不能抛出异常,而子类型抛出了异常,就会导致程序运行的失败。
虽然很好理解,但你可能会有个疑问,我的子类型不都是继承自父类型,咋就能违反 LSP 呢?这个 LSP 是不是有点多此一举呢?
我们来看个例子,有不少的人经常写出类似下面这样的代码:
void handle(final Handler handler) {
if (handler instanceof ReportHandler) {
// 生成报告
((ReportHandler)handler).report();
return;
}
if (handler instanceof NotificationHandler) {
//