定义2:所有引用基类的地方必须能透明地使用其子类的对象。
Liskov于1987年提出了一个关于继承的原则“Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.”——“继承必须确保超类所拥有的性质在子类中仍然成立。”也就是说,当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系。
举一个例子:生物学的分类体系中把企鹅归属为鸟类,我们模仿这个体系,设计出这样的类和关系。类“鸟”中有个方法fly,企鹅自然也继承了这个方法,可是企鹅不能飞阿,于是,我们在企鹅的类中覆盖了fly方法,告诉方法的调用者:企鹅是不会飞的。这完全符合常理。但是,这违反了LSP,企鹅是鸟的子类,可是企鹅却不能飞!
里氏替换原则是继承关系方面的相关设计原则,用于去度量继承关系的质量。LSP并没有提供解决这个问题的方案,而只是提出了这么一个问题,1988年,B. Meyer提出了Design by Contract(契约式设计)理论,较好的解决了这个问题。
前置条件:每个方法调用之前,该方法应该校验传入参数的正确性,只有正确才能执行该方法,否则认为调用方违反契约,不予执行。这称为前置条件(Pre-condition)。
后置条件:一旦通过前置条件的校验,方法必须执行,并且必须确保执行结果符合契约,这称之为后置条件(Post-condition)。
不变式:对象本身有一套对自身状态进行校验的检查条件,以确保该对象的本质不发生改变,这称之为不变式(Invariant)。
为了满足LSP,当存在继承关系时,子类中方法的前置条件必须与超类中被覆盖的方法的前置条件相同或者更宽松;而子类中方法的后置条件必须与超类中被覆盖的方法的后置条件相同或者更为严格。后面又增加了一些限制:继承并且覆盖超类方法的时候,子类中的方法的可见性必须等于或者大于超类中的方法的可见性,子类中的方法所抛出的受检异常只能是超类中对应方法所抛出的受检异常的子集合。
有人会提出疑问,为什么要遵守这原则,遵守这原则有什么好处?或则说不遵守这原则有什么坏处呢?
我们知道,OCP 是面向对象设计原则的最高原则,我们的系统要求设计成不修改原有代码的情况下对扩展开放,如果不遵守这原则,就可能破坏了OCP原则,比如:你设计了类A(基类),如果类A的方法f1的入参范围是1,2,3,别人的客户端程序用你的类A时,调用方法f1,后来为了业务需要,你又扩展了子类B,也实现了f1方法,但是你的入参要求是1,2。然后将B替换A时,发现原客户端的代码运行出现问题了,原来传入3时运行是正常的,而现在替换成B的对象后,3缺出现异常,这时就要求修改客户端的代码,这就违反了OCP原则了。
原文链接:https://www.f2er.com/javaschema/285842.html