复用与里氏替换原则(LSP)

设计可复用的类

以面向对象编程的思想设计可复用的类的关键要点有:

  • 封装与信息隐藏
  • 子类继承与重写
  • 多态、子类与重载
  • 泛型
    有两个基本原则:
  1. 行为子类与里氏替换原则
  2. 委托与组成

行为子类与里氏替换原则

行为子类

子类多态:对于不同类型的对象,用户代码可用同意的方式处理。假设有一个类Dog是Animal类型的子类,那么当存在一个适用于Dog类型的表达式,这个表达式也能被Animal类型的其他子类复用。例如:

Animal a = new Animal();
Animal d1 = new Dog();
Dog d2 = new Dog();

在可以使用Animal类型对象的场景下,使用d1与d2都没有问题。

Java中的静态类型检查

  • 子类中可以增加父类没有的方法,但是不可删除父类中的方法。
  • 子类中需要实现所有未定义实现的方法。
  • 子类中的重写方法必须返回与父类中原方法返回值相同的类型或其子类。
  • 子类中的重写方法必须使用与父类声明中相同类型的参数。
  • 子类中重写的方法不能抛出额外的异常。

子类中的规约性应与父类中一致或更强

A specification S2 is stronger than or equal to a specification S1 if and only if S2’s precondition is weaker than or equal to S1’s, and S2’s postcondition is stronger than or equal to S1’s, for the states that satisfy S1’s precondition.

里氏替换原则(LSP)

定义1:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。
定义2:所有引用基类的地方必须能透明地使用其子类的对象。
具体来讲:

  1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法;
  2. 子类中可以增加自己特有的方法。

子类必须实现父类所有非私有的属性和方法,或子类的所有非私有属性和方
法必须在父类中声明。即,子类可以有自己的“个性”,这也就是说,里氏
代换原则可以正着用,不能反着用(在项目中,采用里氏替换原则时,尽量
避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就
很难调和了)。根据里氏代换原则,为了保证系统的扩展性,在程序中通常
使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的
声明,则无法在以父类定义的对象中使用该方法。
尽量把父类设计为抽象类或者接口。让子类继承父类或实现父接口,并实现
在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地
扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增
加一个新的子类来实现。

在编程中,LSP有如下限制:

  • 子类的前置条件(precondition)不能被强化
  • 子类的后置条件(Postcondition)不能被弱化
  • 子类中的不变量(Invariant)需与父类的保持一致

协变(Covariance)

协变是在计算机科学中,描述具有父/子型别关系的多个型别通过型别构造器、构造出的多个复杂型别之间是否有父/子型别关系的用语。例如:

class P { //父类
  Object a() {...} //返回值为Object
}

class C extends P { //子类
  @Override
  String a() {...} //方法的返回值更加具体
}

同理,对抛出的异常,子类中抛出的异常类型应该比父类中的更加具体。

逆变(Contravariance)

逆变即协变的对立面,协变要求类型更加具体,而逆变要求类型更加抽象。同样以上面的代码为例子:

class P { //父类
  void a(String s) {...} //参数类型为String
}

class C extends P { //子类
  @Override
  void a(Object s) {...} //参数类型更加抽象,为String类型的父类Object
}

在LSP原则中,子类中的方法的返回值与异常的类型要求协变,方法的参数类型要求逆变。

泛型中的LSP

注意:尽管Integer是Number类型的子类,但是Collection 不是Collection类型的子类

泛型通配符
  1. 普通通配符,常规使用 `List`
  2. 下限通配符,规定使用的类型应为A及其父类
  3. 上限通配符,规定使用的类型应为A及其子类
posted @ 2021-06-30 14:06  喵团  阅读(163)  评论(0)    收藏  举报