里氏替换原则 - 没有重写/虚拟方法?
我对里氏替换原则的理解是,基类的某些属性是正确的,或者基类的某些实现的行为对于派生类也应该是正确的。
我想这意味着当在基类中定义方法时,永远不应该在派生类中重写该方法 - 因为替换基类而不是派生类会产生不同的结果。我想这也意味着,拥有(非纯)虚拟方法是一件坏事?
我想我可能对原理理解有误。如果我不这样做,我就不明白为什么这个原则是好的实践。有人可以向我解释一下吗?谢谢
My understanding of the Liskov substitution principle is that some property of the base class that is true or some implemented behaviour of the base class, should be true for the derived class as well.
I guess this would mean when a method is defined in a base class, it should never be overrided in the derived class - since then substituting the base class instead of the derived class would give different results. I guess this would also mean, having (non-pure) virtual methods is a bad thing?
I think I might have a wrong understanding of the principle. If I don't, I do not understand why is this principle good practice. Can someone explain this to me? Thanks
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
里氏替换原则完全允许子类重写基类中的方法。
这可能过于简化了,但我记得它是“子类应该不需要更多,承诺也不会更少”
如果客户端使用超类
ABC
使用方法something(int i)
,那么客户端应该能够毫无问题地替换ABC
的任何子类。与其从变量类型的角度来考虑这个问题,不如从前置条件和后置条件的角度来考虑它。如果上面
ABC
基类中的something()
方法有一个宽松的前提条件,允许任何整数,那么ABC 的所有子类
必须还允许任何整数。子类GreenABC
不允许向something()
方法添加要求参数为正整数的附加前提条件。这将违反里氏替换原则(即需要更多)。因此,如果客户端使用子类BlueABC
并将负整数传递给something()
,那么如果我们需要切换到GreenABC
,客户端也不会中断。 。相反,如果基本
ABC
类something()
方法具有后置条件 - 例如保证它永远不会返回零值 - 那么所有子类也必须遵守相同的条件后置条件或者它们违反了里氏替换原则(即承诺较少)。我希望这有帮助。
Subclasses overriding methods in the base class are totally allowed by the Liskov Substituion Principle.
This might be simplifying it too much, but I remember it as "a subclass should require nothing more and promise nothing less"
If a client is using a superclass
ABC
with a methodsomething(int i)
, then the client should be able to substitute any subclass ofABC
without problems. Instead of thinking about this in terms of variable types, perhaps think about it in terms of preconditions and postconditions.If our
something()
method in theABC
base class above has a relaxed precondition that permits any integer, then all subclasses ofABC
must also permit any integer. A subclassGreenABC
is not allowed to add an additional precondition to thesomething()
method that requires the parameter to be a positive integer. This would violate the Liskov Substitution Principle (i.e., requiring more). Thus if a client is using subclassBlueABC
and passing negative integers tosomething()
the client won't break if we need to switch toGreenABC
.In reverse, if the base
ABC
classsomething()
method has a postcondition - such as guaranteeing it will never return a value of zero - then all subclasses must also obey that same postcondition or they violate the Liskov Substitution Principle (i.e., promising less).I hope this helps.
有一个流行的例子说,如果它像鸭子一样游泳,像鸭子一样嘎嘎叫,但需要电池,那么它就违反了里氏替换原理。
简而言之,您有一个正在被某人使用的 Duck 基类。然后,通过引入 PlasticDuck 来添加层次结构,其具有与鸭子相同的覆盖行为(如游泳、嘎嘎叫等),但需要电池来模拟这些行为。这本质上意味着您正在为子类的行为引入一个额外的先决条件,以要求电池执行与之前由没有电池的 Base Duck 类所做的相同行为。这可能会让 Duck 类的使用者感到惊讶,并且可能会破坏围绕 Base Duck 类的预期行为构建的功能。
这是一个很好的链接 - http:// lassala.net/2010/11/04/liskov-substitution-principle 的一个好例子/
There is one popular example which says if it swims like a duck, quack likes a duck but requires batteries, then it breaks Liskov Substitution Principle.
Put it simply, you have a base Duck class which is being used by someone. Then you add hierarchy by introduction PlasticDuck with same overridden behaviors (like swimming, quacking etc.) as of a Duck but requires batteries to simulate those behaviors. This essentially means that you are introducing an extra pre-condition to the behavior of Sub Class to require batteries to do the same behavior that was earlier done by the Base Duck class without batteries. This might catch the consumer of your Duck class by surprise and might break the functionality built around the expected behavior of Base Duck class.
Here is a good link - http://lassala.net/2010/11/04/a-good-example-of-liskov-substitution-principle/
不,它告诉您应该能够以与其基类相同的方式使用派生类。有很多方法可以重写方法而不破坏它。一个简单的例子,C#中的GetHashCode()是所有类的基础,并且所有类仍然可以用作“对象”来计算哈希码。据我所知,打破规则的一个典型例子是从 矩形 派生出 方形 ,因为方形不能同时具有宽度和高度 - 因为设置一个会改变另一个,因此它不再符合矩形规则。但是,您仍然可以使用 .GetSize() 拥有基本形状,因为所有形状都可以执行此操作 - 因此任何派生形状都可以替换并用作形状。
No, it tells that you should be able to use derived class in the same way as its base. There're many ways you can override a method without breaking this. A simple example, GetHashCode() in C# is in base for ALL classes, and still ALL of them can be used as "object" to calculate the hash code. A classic example of breaking the rule, as far as I remember, is derivin Square from Rectangle, since Square can't have both Width and Height - because setting one would change another and thus it's no more conforms to Rectangle rules. You can, however, still have base Shape with .GetSize() since ALL shapes can do this - and thus any derived shape can be substituted and used as Shape.
如果您更改基本方法定义的任何行为,则重写会破坏里氏替换原则。这意味着:
比基本方法。
意味着后置条件
父方法。其中后置条件
由以下部分组成:a) 所有侧面
由方法执行和 b) 引起的影响
返回表达式的类型和值。
根据这两个要求,您可以暗示子方法中的任何新功能如果不影响超级方法的预期,则不会违反该原则。这些条件允许您在需要超类实例的地方使用子类实例。
如果不遵守这些规则,则类将违反 LSP。一个经典的示例是以下层次结构:类
Point(x,y)
,类ColoredPoint(x,y,color)
扩展了Point(x,y)
和ColoredPoint
中的重写方法equals(obj)
反映了颜色的相等性。现在,如果有一个Set
实例,他可以假设该集合中具有相同坐标的两个点相等。重写方法equals
的情况并非如此,而且一般来说,没有办法在不破坏 LSP 的情况下扩展可实例化类并添加在equals
方法中使用的方面。因此,每次违反此原则时,您都会隐式引入一个潜在的错误,该错误会揭示代码所期望的父类的不变性何时不满足。然而,在现实世界中,通常没有不违反 LSP 的明显设计解决方案,因此可以使用例如 @ViolatesLSP 类注释来警告客户端使用类实例是不安全的在多态集或依赖里氏替换原理的任何其他类型的情况下。
Overriding breaks Liskov Substitution Principle if you change any behavior defined by a base method. Which means that:
child method should be not stronger
than for the base method.
implies a postcondition for the
parent method. Where a postcondition
is formed by: a) all side
effects caused by a method execution and b)
type and value of a returned expression.
From these two requirements you can imply that any new functionality in a child method that does not affect what is expected from a super method does not violate the principle. These conditions allow you to use a subclass instance where a superclass instance is required.
If these rules are not obeyed a class violates LSP. A classical example is the following hierarchy: class
Point(x,y)
, classColoredPoint(x,y,color)
that extendsPoint(x,y)
and overridden methodequals(obj)
inColoredPoint
that reflects equality by color. Now if one have an instance ofSet<Point>
he can assume that two points with the same coordinates are equal in this set. Which is not the case with the overridden methodequals
and, in general, there is just no way to extend an instantiable class and add an aspect used inequals
method without breaking LSP.Thus every time you break this principle you implicitly introduce a potential bug that reveals when invariant for a parent class that is expected by the code is not satisfied. However, in real world often there is no obvious design solution that does not violate LSP, so one can use, for example,
@ViolatesLSP
class annotation to warn a client that it is not safe to use class instances in a polymorphic set or in any other kind of cases that rely on the Liskov substitution principle.我认为您描述原理的方式实际上是正确的,只有重写纯虚拟或抽象方法才能确保您不会违反它。
但是,如果从客户端的角度来看原理,即采用基类引用的方法。如果此方法无法告诉(当然不会尝试也不需要找出)传入的任何实例的类,那么您也没有违反该原则。因此,重写基类方法可能并不重要(某些装饰器可能会执行此操作,并在此过程中调用基类方法)。
如果客户端似乎需要找出传入的实例的类,那么您将陷入维护噩梦,因为您实际上应该只是添加新类作为维护工作的一部分,而不是修改现有例程。 (另请参阅OCP)
I think that you're literally correct in the way you describe the principle and only overriding pure virtual, or abstract methods will ensure that you don't violate it.
However, if you look at the principle from a client's point of view, that is, a method that takes a reference to the base class. If this method cannot tell (and certainly does not attempt to and does not need to find out) the class of any instance that is passed in, then you are also not violating the principle. So it may not matter that you override a base class method (some sorts of decorators might do this, calling the base class method in the process).
If a client seems to need to find out the class of an instance passed in, then you're in for a maintenance nightmare, as you should really just be adding new classes as part of your maintenance effort, not modifying an existing routine. (see also OCP)
最初的原则:
“这里想要的是类似于以下替换属性:如果对于每个 S 类型的对象 o1 都有一个 T 类型的对象 o2,这样对于所有用 T 定义的程序 P,行为当o1替换o2时,P的不变,则S是T的子类型。”。
这个词就是行为。 “前置条件和后置条件”的理解对于良好的设计很有用,但与 LSP 无关。
让我们检查一下“前置条件和后置条件”理论的总结:
与 LSP 无关的一个迹象是:VOID 方法怎么样? VOID 没有 OUTPUT 参数。该规则如何应用于 VOID 方法?根据这个规则,我们如何保证 VOID 方法中遵守 LSP?
LSP 指的是行为。当子类继承自超类时,您必须使用一些技巧来使其工作,结果会改变您正在破坏 LSP 的程序的行为。
LSP是关于行为的,正方形x矩形的经典例子可以帮助我们理解。其实就是鲍勃叔叔用的例子。
您从 Rectangle 继承 Square 并覆盖 SetHeight 和 SetWidth 以强制 Square 充当 Square,即使它是一个矩形(通过继承)。
当用户调用 SetHeight 时,不会期望宽度发生变化......但会发生变化,这会改变预期的行为并破坏 LSP。
这是 Virtuals x LSP 的问题
The original principle:
"What is wanted here is something like the following substitution property: 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.".
The word is behavior. The "preconditions and postconditions" understanding is useful for a good design but is not related to LSP.
Let's check this summary of "preconditions and postconditions" theory:
An indication that it has nothing to do with LSP is: what about VOID methods? VOID does not have OUTPUT parameters. How could this rule be applied to VOID methods? How, according to this rule, could we guarantee to be complying with LSP in VOID methods?
LSP refers to Behavior. When a subclass inherits from a superclass and you have to use some trick to make this work, and the result change the behavior of the program you are breaking LSP.
LSP is about behaviour and the clasic example of Square x Rectangle help us to understand. In fact is the example used by Uncle Bob.
The you inherit Square from Rectangle and overrides SetHeight and SetWidth to force Square act as a Square even if it's a rectangle (by inheritance).
When the user calls SetHeight do not expect Width change.... but will change and this change the expected behavior and break LSP.
This is the problem with Virtuals x LSP