里式替换原则Liskov Substitution Principle
里氏替换原则
“If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.”。
也就是说,任何父类可以出现的地方,子类一定可以出现。里氏替换原则是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
如何进行设计
而要在程序设计时满足里式替换原则,就要考虑到规约了。
既然子类可以对父类进行替换,而保证程序行为不变。那么只能说明子类的规约一定等于或强于父类的规约。从规约的角度来考虑设计问题显然思路就简单多了。
前置条件:子类比父类更弱或相同
后置条件:子类比父类更强或相同
不变量要保持
可见,当子类型规约强于或等于父类型,满足里氏替换原则时,父类型→子类型:越来越具体。
那么由此再来思考子类型参数类型、返回值和异常
子类型参数:与前置条件相关,那么显然应该不变或向相反的方向变化(逆变),不变或越来越抽象。
返回值和异常:与后置条件相关,那么显然应该不变或向相同的方向变化(协变),不变或越来越具体。
那么我们根据这些再加上Java中编译器规则对于子类型的要求来进行子类设计,就能设计出复用性较好的子类型了。
Java中编译器规则:
子类型可以增加方法,但不可删除
子类型需要实现抽象类型中的所有未实现方法
子类型中重写的方法返回值必须与父类相同或符合co-variance(协变)
子类型中重写的方法必须使用同样类型的参数或者符合contra-variance(逆变)的参数
子类型中重写的方法不能抛出额外的异常
误区例子
我们都知道,正方形是特殊的长方形,那我们在设计正方形的时候就可以直接继承长方形来实现正方形吗?
显然不可以,因为正方形和长方形的操作显然是不一样的,长方形需要设置长和宽两个参数,而正方形只有边长这一个参数。正方形继承长方形显然违背了里氏替换原则,在实际设计中肯定会出问题。