Liskov替换原则 - 没有覆盖/虚拟方法?

时间:2009-11-14 18:30:37

标签: design-principles solid-principles liskov-substitution-principle

我对Liskov替换原则的理解是,对于派生类,基类的某些属性为true或某些已实现的基类行为也应该为true。

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

我想我可能对这个原则有错误的理解。如果我不这样做,我不明白为什么这个原则是好的做法。谁可以给我解释一下这个?感谢

5 个答案:

答案 0 :(得分:51)

Liskov Substituion Principle完全允许基类中的子类重写方法。

这可能会过多地简化它,但我记得它是 “一个子类应该不再需要承诺”

如果客户端使用带有方法ABC的超类something(int i),则客户端应该能够毫无问题地替换ABC的任何子类。而不是根据变量类型来考虑这一点,或许可以根据前提条件和后置条件来考虑它。

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

相反,如果基类ABCsomething()方法具有后置条件 - 例如保证它永远不会返回零值 - 那么所有子类也必须遵循相同的后置条件或者它们违反了Liskov替代原则(即承诺减少)。

我希望这会有所帮助。

答案 1 :(得分:10)

有一个流行的例子说,如果它像鸭子一样游泳,嘎嘎喜欢鸭子但需要电池,那么就打破了Liskov替代原则。

简单地说,你有一个正在被某人使用的基础Duck类。然后你通过介绍PlasticDuck添加层次结构与鸭子相同的重写行为(如游泳,嘎嘎等),但需要电池来模拟这些行为。这实际上意味着您要为Sub Class的行为引入一个额外的前提条件,要求电池执行与之前没有电池的Base Duck类所做的相同的行为。这可能会让您的Duck类的消费者感到意外,并且可能会破坏围绕Base Duck类的预期行为构建的功能。

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

答案 2 :(得分:7)

不,它告诉您应该能够以与其基础相同的方式使用派生类。有很多方法可以覆盖方法而不会破坏它。一个简单的例子,C#中的GetHashCode()是所有类的基础,并且它们中的所有类都可以用作“对象”来计算哈希码。据我所知,破坏规则的一个典型例子是从矩形中导出Square,因为Square不能同时具有宽度和高度 - 因为设置一个会改变另一个,因此它不再符合Rectangle规则。但是,您仍然可以使用.GetSize()来创建基本Shape,因为所有形状都可以执行此操作 - 因此任何派生的形状都可以替换并用作Shape。

答案 3 :(得分:3)

如果更改基本方法定义的任何行为,则覆盖中断Liskov替换原则。这意味着:

  1. 最弱的前提条件 孩子的方法应该不强 而不是基本方法。
  2. 子方法的后置条件 意味着一个后置条件 父方法。后置条件的地方 由以下形成: a)所有方面 由方法执行和 b)引起的效果 返回表达式的类型和值。
  3. 根据这两个要求,您可以暗示子方法中的任何新功能不会影响超级方法的预期,并不违反原则。这些条件允许您使用需要超类实例的子类实例。

    如果不遵守这些规则,则类违反LSP。一个典型的例子是以下层次结构:类Point(x,y),类ColoredPoint(x,y,color),它扩展Point(x,y)并覆盖equals(obj)中反映颜色相等的ColoredPoint方法Set<Point>。现在,如果有一个equals的实例,他可以假设在这个集合中具有相同坐标的两个点相等。覆盖方法equals的情况并非如此,并且通常无法扩展可实例化的类并添加@ViolatesLSP方法中使用的方面而不破坏LSP。

    因此,每当您违反此原则时,您隐式引入了一个潜在的错误,该错误揭示了代码所期望的父类的不变性何时不满足。然而,在现实世界中,通常没有明显的设计解决方案不违反LSP,因此可以使用例如{{1}}类注释来警告客户端在多态集中使用类实例是不安全的。或任何其他依赖Liskov替代原则的案例。

答案 4 :(得分:1)

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

但是,如果从客户端的角度来看这个原则,那就是一个引用基类的方法。如果这个方法无法告诉(当然不会尝试并且不需要查找)传入的任何实例的类,那么您也没有违反该原则。因此,覆盖基类方法可能并不重要(某些类型的装饰器可能会这样做,在过程中调用基类方法)。

如果客户端似乎需要找出传入的实例的类,那么您就是在进行维护噩梦,因为您应该在维护工作中添加新类,而不是修改现有的常规。 (另见OCP