相同接口不保证相同行为

Liskov 在1987年的讨论中区分了类型之间的语义关系与类继承这样的语言机制。继承可以用来实现子类型,也可以仅用于复用代码,两者不能直接画等号。

例如,栈和队列都可以提供“加入”和“取出”,但一个按后进先出,另一个按先进先出。若调用方依赖取出顺序,改用另一种结构就可能破坏程序。

参考:[1]

不能提高门槛或减少保证

按照 Eiffel 的契约继承规则,重定义方法不能加强调用前必须满足的条件,也不能削弱调用后应当满足的保证。父类型允许的调用,不能仅因对象被替换就突然变成非法。

例如,父接口若允许任意非负金额,子实现额外要求金额必须超过某个下限,就提高了调用门槛。内部算法可以不同,但向调用者承诺的有效范围必须得到保留。

参考:[2]

规范决定了需要保持什么

类不变式描述对象在规定观察点必须满足的状态条件,继承时也需要保持。可替换并不是要求内部表示、每条指令或所有无关细节完全一致,而是保留规范中对使用者重要的行为。

这使规范本身成为设计的一部分:只有名字而没有结果、异常或状态变化的约定,难以判断替换是否可靠。类型检查与契约检查各有作用,不能把通过编译当成完整的行为证明。

参考:[1] [2]