LSP原则中的逆变和协变

2023-03-09,,

在复习过程中,LSP原则是个很重要的内容。这里先给出LSP原则的定义。

LSP定义
Functions that use pointers or referrnces to base classes must be able to use objects of derived classes without knowing
it.(所有引用基类的地方必须能透明地使用其子类的对象。)

这个的主要作用就是规范继承时子类的一些书写规则。其主要目的就是保持父类方法不被覆盖。

也就是所有引用父类的地方,替换子类进去,不会有任何影响。

子类必须完全实现父类的方法
子类可以有自己的个性
覆盖或实现父类的方法时输入参数可以被放大
覆盖或实现父类的方法时输出结果可以被缩小

LSP是子类型关系的一个特殊定义,称为(强)行为子类型化。在编程语言中,LSP依赖于以下限制:
前置条件不能强化
后置条件不能弱化
不变量要保持或增强
子类型方法参数:逆变
子类型方法的返回值:协变
异常类型:协变

这里出现了两个生词:协变和逆变。本篇文章主要说一下这俩是啥。

逆变与协变综述:如果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)相互之间没有继承关系。

协变(Co-variance)
  父类型->子类型:越来越具体(specific)。
  在LSP中,返回值和异常的类型:不变或变得更具体 。

逆变(Contra-variance)

  父类型->子类型:越来越抽象。

  参数类型:要相反的变化,不变或越来越抽象。

来看一下应用:

数组是协变的:一个数组A[ ] ,可能包含了A类型的实例或者A的任何子类型的实例

1 Number[] numbers = new Number[2];
2 numbers[0] = new Integer(10);
3 numbers[1] = new Double(3.14);
4
5 Integer[] myInts = {1,2,3,4};
6 Number[] myNumber = myInts;
7
8 myNumber[0] = 3.14; //run-time error!

第8行会报错的原因是myNumber指向的是一个Integer[] 而不是Number[],对Interger赋值一个double类型的,不是协变

其实在继承中只需要注意LSP原则即可,这是不矛盾的。

对于参数,也就是前置条件,应当逆变,把前置条件范围放宽。

对于返回值,也就是后置条件,应当协变,把后置条件范围缩小。

这两者加起来,也就是规约变强,符合LSP。

LSP原则中的逆变和协变的相关教程结束。

《LSP原则中的逆变和协变.doc》

下载本文的Word格式文档,以方便收藏与打印。