哈工大软件构造复习——LSP原则,协变和逆变
(防扒链接)
写在前面
在复习软件构造课程的过程中,LSP原则,协变和逆变是课程后期的重点之一,鉴于其难度较高,特总结此篇博客以更好地学习这些知识。
一、LSP原则
LSP原则,即Liskov Substitution Principle,常译为里氏替换原则:
只要父类能出现的地方,子类就可以出现,并且替换为子类也不会产生任何错误或异常。
常用如下:
- 子类型可以增加方法,但不可删
- 子类型需要实现抽象类型中的所有未实现方法
- 子类型中重写的方法必须有相同或子类型的返回值或者符合协变的参数
- 子类型中重写的方法必须使用同样类型的参数或者符合逆变的参数。
- 子类型中重写的方法不能抛出额外的异常,子类型也可以不抛出异常。异常必须满足协变。
- Same or stronger invariants 更强的不变量
- Same or weaker preconditions 更弱的前置条件
- Same or stronger postconditions 更强的后置条件
在实际操作中需要注意以下三种情况:
(1)所有使用基类的地方必须能透明地使用子类替换,而程序的行为没有任何变化(不会产生运行结果错误或异常)。只有这样,父类才能被真正复用,而且子类也能够在父类的基础上增加新的行为。也只有这样才能正确的实现多态。
(2)当一个类继承了另一个类时,子类就拥有了父类中可以继承下来的属性和操作。但如果子类覆盖了父类的某些方法,那么原来使用父类的地方就可能会出现错误,因为表面上看,它调用了父类的方法,但实际运行时却调用了被子类覆盖的方法,而这两个方法的实现可能不一样,这就不符合LSP原则。
(3)LSP原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
LSP原则简而言之就是规范继承时子类的一些书写规则:
前置条件不能强化 后置条件不能弱化 不变量要保持或增强
子类型方法参数:逆变子类型方法的返回值:协变
异常类型:协变
这里出现了两个易混淆的名词:协变和逆变,在下文将给出介绍。
二、协变和逆变
逆变与协变用来描述类型转换(type transformation)后的继承关系
定义:如果A、B表示类型,f(⋅)表示类型转换,≤表示继承关系
(比如,A≤B表示A是由B派生出来的子类)
f(⋅)是逆变(contravariant)的,当A ≤ B时有f(B) ≤ f(A)成立;
f(⋅)是协变(covariant)的, 当A ≤ B时有f(A) ≤ f(B)成立;
f(⋅)是不变(invariant)的, 当A ≤ B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。
1、协变
如果A是B的子类,那么A中的类型T也是B中类型T’的子类,这就是协变。
父类型->子类型:越来越具体(specific)。
在LSP中,返回值和异常的类型:不变或变得更具体 。
2、逆变
如果A是B的子类,但是A中的类型T是B中类型T’的祖先类型,那么就是逆变。
父类型->子类型:越来越抽象(abstract)。
参数类型:要相反的变化,不变或越来越抽象。