Liskov substitution principle, preconditions and abstract methods
Liskov替代原则(LSP)表示:
Preconditions cannot be strengthened in a subtype.
在C中,我可能违反以下整个原则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class A
{
public virtual void DoStuff(string text)
{
Contract.Requires(!string.IsNullOrEmpty(text));
}
}
public class B : A
{
public override void DoStuff(string text)
{
Contract.Requires(!string.IsNullOrEmpty(text) && text.Length > 10);
}
} |
但是,如果A.DoStuff是abstract方法,会发生什么情况:
1 2 3 4 5 6 7 8 9 10 11 12
| public class A
{
public abstract void DoStuff(string text);
}
public class B : A
{
public override void DoStuff(string text)
{
Contract.Requires(!string.IsNullOrEmpty(text));
}
} |
现在,A.DoStuff是无契约的。或者它的合同只是允许的。
那么,B.DoStuff前提条件是否违反了Liskov替换原则?
- A.DoStuff没有先验,而B.DoStuff有先验,所以你加强了它。
- @李:讨论可能是,如果一个抽象的方法,仅仅是元数据,不能被认为不提供前提条件,因为它没有行为,它不提供前提条件的事实不应该意味着它没有前提条件。我相信这是一个简单的例子,但它可能需要一个复杂的分析来确定它是否真的破坏了LSP…
- @你为什么不回答?
- 我不知道您所说的提供元数据的抽象方法是什么意思,但是如果一个方法没有显式地声明任何前提条件,那么它就没有任何前提条件。
- @zerkms-抽象方法可以具有与任何其他方法相同的前/后条件。
- @李,你应该详细说明你最后的陈述
- @mat&237;asfidmraizer请参见ContractClass和ContractClassFor属性
- @李与合同类,甚至接口可以有前置、后置条件和不变量…
- 是的,这就是它们的确切含义——在对待"普通"和抽象方法或接口方面应该没有区别:如果它们没有前提,那么添加任何*加强它。
- 因为我越想越确信是的,事实上这是违反LSP的,我的答案可能是错误的。我还在考虑……但我会取消删除答案,打开另一个观点
- 我是说,前/后的条件必须是明确的,所以不提供任何条件,你是含蓄地说没有条件。A.DoStuff没有先决条件,B.DoStuff做B加强先决条件,违反LSP。A.DoStuff是抽象的,与此无关,因为抽象方法可以像其他方法一样具有前/后条件。
- @Zerkms您在为相反的观点辩护的地方发表了一些评论,认为接口方法的实现可能会破坏LSP,现在您声明您将在不违反LSP的情况下处理常规、抽象和接口方法,而没有任何区别。我之所以打开这个问答,是因为我觉得这不是100%清楚的,我们可以在论点中犯错误,这是为了得到我(甚至我们)关心的问题的令人信服的答案。
- @因此,当您创建给定接口的契约类时,也会违反LSP。抽象方法和接口方法几乎是同一个怪物:在C中,它们只是元数据。
- 我不明白你所说的"元数据"是什么意思。如果您创建了一个契约类并为抽象方法或接口提供了一个契约,那么所有的实现都必须尊重这一点,即不加强前提条件或削弱后置条件。在您的示例中,您没有为A.DoStuff提供任何前提条件,因此没有,因此任何子类都不能提供任何前提条件。
- @Mat&237;AsFidemraizer我放弃了它,因为我忘记了它在技术上是可能的(我个人认为抽象方法和接口的前/后条件都很笨拙,因此从未使用过它)。
- @在我的例子中,我发现契约类非常有用,您可以定义如何实现接口或抽象类,而不仅仅是实现什么
- 现在,A.DoStuff是无契约的。不,不,不。合同是A!一般来说,每个基类都是其派生类的契约。
这取决于合同的定义。
LSP是一个理论结构,它不依赖于特定的语言或实现,例如C的"代码契约"特性。
合同的定义如下:
- 方法名称
- 方法参数名称
- 方法注释
- 返回类型和方法参数类型
- "明确"合同,如Contract.Requires。
最后两个将由编译器验证。然而,前三个也可以是合同的一部分!请考虑以下示例:
1 2 3 4 5 6 7
| public interface StuffContainer
{
void Add(string text);
// Removes a string that has previously been added.
void Remove(string text);
} |
Remove方法的名称和文档定义了一个明确的前提条件。在实现中验证要删除的字符串以前是否已添加,不会违反LSP。验证字符串是否至少有5个字符将违反LSP。
- 型请注意,我想解释一下C和代码契约的整个问题,以便根据实际示例讨论这个主题。
- 型不管怎样,你的决心是我在我的问题中解释的整个案例不一定会违反LSP?
- 型@mat&237;asfidmraizer:如果您的方法真的被称为DoStuff,并且没有进一步的文档,那么它确实违反了LSP(第一个和第二个示例)。如果您的方法实际上被称为DoStuffOnLongString,并且您在某个地方定义了"long string"是一个超过10个字符的字符串,那么no,LSP没有被违反(在第一个或第二个示例中都没有)。
- 型我的问题来自于另一个问题:stackoverflow.com/a/40957842/411632请看我的答案。@USR在这里提出了一个问题:它违反了LSP。我不是说它没有违反LSP,但我发现提出一个新的问答来记录/分析抽象方法的特定情况是很有趣的。
是的,你可以很容易地打破这个原则,不仅仅是在C。
它只陈述:
Subtype Requirement: Let phi(x) be a
property provable about objects x of type T. Then
phi(y) should be true for
objects y of type S where S is a subtype of T.
在您的示例中,类型B不满足提供与短文本一起工作的方法DoStuff的属性,尽管它的超类型A实现了它。所以这一原则被违反了。
坚持这一原则取决于程序员。属性也可以是"它做了正确的事情",您可以很容易地通过拥有具有错误方法实现的子类型来破坏它。
- 型我说C是因为我想用C来介绍一个代码片段!:)
- 型嗯,当你谈到我的例子时,你是在考虑抽象案例吗?
- 型我认为抽象类没有对象。所以这个原则不适用。
- 型所以你和@inbetween在同一行。
- 型另一方面,我们可以说,派生A的第一个类之后的下一个层次结构级别是应该应用lsp的类。好吧,事实上,任何从A开始的等级制度都是这样运作的
我认为不是。定义的抽象方法没有前提条件,因为没有实现。如果实现一个接口会破坏LSP,这与争论是一样的。
说EDOCX1[1]是反的是一个不真实的前提。A.DoSomehing()是未定义的,因此只有在其签字之后才能有合同。
- 型我相信你的答案在比较抽象方法和接口方法时,会引入一个很好的方向。