关于Liskov替换原则:Liskov替换原则 – 没有覆盖/虚拟方法?

Liskov substitution principle - no overriding/virtual methods?

我对Liskov替换原则的理解是,对于派生类来说,基类的某些属性是真的,或者一些基类的实现行为也是真的。

我想这意味着当一个方法在一个基类中定义时,它不应该在派生类中被重写,因为替换基类而不是派生类会产生不同的结果。我想这也意味着,拥有(非纯)虚拟方法是一件坏事?

我想我可能对这一原则的理解有误。如果我不这样做,我不明白为什么这个原则是好的实践。有人能给我解释一下吗?谢谢


Liskov替换原则完全允许基类中的子类重写方法。

这可能简化得太多了,但我记得它是"一个子类不需要更多,承诺更少"。

如果客户使用方法something(int i)使用一个超类ABC,那么客户应该能够毫无问题地替换ABC的任何子类。与其用变量类型来考虑这个问题,不如用先决条件和后条件来考虑它。

如果上述ABC基类中的something()方法具有允许任何整数的宽松前提,那么ABC的所有子类也必须允许任何整数。不允许子类GreenABC向要求参数为正整数的something()方法添加额外的前提条件。这将违反里斯科夫替代原则(即需要更多)。因此,如果客户机正在使用BlueABC子类并向something()传递负整数,那么如果我们需要切换到GreenABC,客户机将不会中断。

相反,如果基ABCsomething()方法具有后置条件(例如保证它永远不会返回零值),则所有子类也必须遵守相同的后置条件,否则它们违反了liskov替换原则(即承诺较少)。

我希望这有帮助。


有一个很流行的例子说,如果它像鸭子一样游泳,江湖骗子像鸭子但需要电池,那么它就打破了里斯科夫的替代原则。

简单地说,您有一个被某人使用的基本duck类。然后通过引入plasticduck来添加层次结构,它与鸭子的行为(如游泳、咯咯叫等)相同,但需要电池来模拟这些行为。这实质上意味着您正在为子类的行为引入一个额外的前提条件,要求电池执行与基本duck类之前在没有电池的情况下执行的相同行为。这可能会让您的duck类的消费者感到惊讶,并可能破坏围绕基本duck类的预期行为构建的功能。

下面是一个很好的链接-http://lassala.net/2010/11/04/a-good-example-of-liskov-substitution-principle/


不,它告诉您应该能够使用派生类,方法与它的基类相同。有很多方法可以在不破坏方法的情况下重写方法。一个简单的例子,c中的getHashCode()是所有类的基础,但它们仍然可以用作计算哈希代码的"对象"。据我所知,打破规则的一个经典例子是从矩形中派生出正方形,因为正方形不能同时具有宽度和高度-因为设置一个会改变另一个,因此它不再符合矩形规则。但是,您仍然可以使用.getSize()使用基形状,因为所有形状都可以这样做,因此任何派生形状都可以被替换并用作形状。


如果更改由基方法定义的任何行为,则重写将破坏liskov替换原则。这意味着:

  • 最薄弱的先决条件子方法不应更强而不是基本方法。
  • 子方法的后置条件表示的后置条件父方法。哪里有后置条件形成于:a)所有侧面方法执行和b)造成的影响返回表达式的类型和值。
  • 从这两个需求中,您可以暗示子方法中的任何新功能,如果不影响对超级方法的期望值,则不会违反该原则。这些条件允许您在需要超类实例的情况下使用子类实例。

    如果不遵守这些规则,类将违反LSP。一个经典的例子是如下的层次结构:类Point(x,y)、类ColoredPoint(x,y,color),它扩展了Point(x,y),并在ColoredPoint中重写了方法equals(obj),反映了颜色的相等性。现在,如果有一个Set的实例,他可以假设在这个集合中具有相同坐标的两个点是相等的。而对于被重写的方法equals则不是这种情况,而且一般来说,在不破坏lsp的情况下,无法扩展可实例化类并添加equals方法中使用的方面。

    因此,每当您违反这个原则时,都会隐式地引入一个潜在的bug,它揭示了代码所期望的父类的不变量何时不满足。然而,在现实世界中,通常没有明显的设计解决方案不违反LSP,因此可以使用@ViolatesLSP类注释来警告客户,在多态集或任何其他依赖Liskov替换原则的情况下使用类实例是不安全的。


    我认为你在描述原则的方式上是完全正确的,只有重写纯虚拟或抽象的方法才能确保你不会违反它。

    但是,如果您从客户机的角度来看这个原则,也就是说,一个引用基类的方法。如果这个方法不能分辨(当然也不会尝试也不需要找出)传入的任何实例的类,那么您也不会违反这个原则。因此,重写基类方法可能无关紧要(某些修饰符可能会这样做,在进程中调用基类方法)。

    如果客户机似乎需要找出传入的实例的类,那么您将陷入维护噩梦,因为您实际上应该只是添加新类作为维护工作的一部分,而不是修改现有的例程。(也见OCP)