简体   繁体   English

Liskov替换原理和接口

[英]Liskov substitution principle and Interface

Does the ICollection<T>.Add() -implementation of arrays break the Liskov substitution principle? ICollection<T>.Add() - 数组的实现是否违反了Liskov替换原则? The method results in a NotSupportedException , which does break the LSP, IMHO. 该方法导致NotSupportedException ,它会破坏LSP,恕我直言。

string[] data = new string[] {"a"};
ICollection<string> dataCollection = data;
dataCollection.Add("b");

This results in 这导致了

Unhandled exception: System.NotSupportedException: Collection was of a fixed size. 未处理的异常:System.NotSupportedException:Collection具有固定大小。

I found a pretty similar question concerning Stream -implementations. 我发现了一个关于Stream -implementations的非常类似的问题。 I open a separate question, because this case is pretty different: Liskov substitution principle and Streams . 我打开一个单独的问题,因为这个案例非常不同: Liskov替换原则和Streams The difference here is that ICollection does not provide a CanAdd -Property or such thing, as the Stream -class does. 这里的区别在于ICollection不像Stream CanAdd那样提供CanAdd -Property或类似的东西。

I can see why you'd think so. 我明白你为什么这么认为。 There's a function that expects a collection, and it expects it to be modifiable. 有一个函数需要一个集合,它期望它是可修改的。 Passing an array will make it fail, so clearly you can't substitute the interface with this particular implementation, right? 传递数组会使它失败,所以很明显你不能用这个特定的实现替换接口,对吧?

Is it a problem? 这是个问题吗? Maybe. 也许。 It depends on how often you expect ideals to hold. 这取决于你期望理想持有的频率。 Are you going to use an array instead of a collection by accident and then be surprised ten years later that it breaks down? 你是不是偶然会使用一个阵列而不是一个集合,然后在十年之后惊讶它会崩溃? Not really. 并不是的。 The type system .NET applications use isn't perfect - it doesn't tell you this particular ICollection<T> usage requires the collection to be modifiable. .NET应用程序使用的类型系统并不完美 - 它没有告诉您这种特定的ICollection<T>用法要求集合可以修改。

Would .NET be better off if arrays didn't pretend to implement ICollection<T> (or IEnumerable<T> , which they also don't "really" implement)? 如果数组没有假装实现ICollection<T> (或IEnumerable<T> ,它们也没有“真正”实现),.NET会更好吗? I don't think so. 我不这么认为。 Is there a way to keep the convenience of arrays "being" ICollection<T> that would also avoid the same LSP violation? 有没有办法保持数组的“方便性” ICollection<T> ,这也可以避免相同的LSP违规? Nope. 不。 The underlying array would still be fixed-length - at best, you'd be violating more useful principles instead (like the fact that reference types are not expected to have referential transparency). 底层数组仍然是固定长度的 - 充其量,你会违反更多有用的原则(比如预期引用类型不具有引用透明性这一事实)。

But wait! 可是等等! Let's look at the actual contract of ICollection<T>.Add . 让我们看一下ICollection<T>.Add的实际合约。 Does it allow for a NotSupportedException to be thrown? 是否允许抛出NotSupportedException Oh yes - quoting MSDN: 哦是的 - 引用MSDN:

[NotSupportedException is thrown if ...] The ICollection is read-only. [如果......则抛出NotSupportedException] ICollection是只读的。

And arrays do return true when you query IsReadOnly . 当您查询IsReadOnly时,数组会返回true。 The contract is upheld. 合同得到维护。

If you consider Stream not to break LSP because of CanWrite , you must consider arrays to be valid collections, since they have IsReadOnly , and it is true . 如果您认为Stream不会因为CanWrite而破坏LSP,则必须将数组视为有效集合,因为它们具有IsReadOnly ,并且它是true If a function accepts a read-only collection and tries adding to it, it's an error in the function. 如果函数接受只读集合并尝试添加它,则该函数中存在错误。 There's no way to specify this explicitly in C#/.NET, so you have to rely on other parts of the contract than just types - eg the documentation for the function should specify that a NotSupportedException (or ArgumentException or whatever) is thrown for a collection that is readonly. 没有办法在C#/ .NET中明确指定它,所以你必须依赖合同的其他部分而不仅仅是类型 - 例如,函数的文档应该指定为集合抛出NotSupportedException (或ArgumentException或其他)这是只读的。 A good implementation would do this test right at the start of the function. 一个好的实现将在函数开始时进行此测试。

One important thing to note is that types aren't quite as constrained in C# as in the type theory where LSP is defined. 需要注意的一件重要事情是,C#中的类型并不像定义LSP的类型理论那样受到限制。 For example, you can write a function like this in C#: 例如,您可以在C#中编写这样的函数:

bool IsFrob(object bobicator)
{
  return ((Bob)bobicator).IsFrob;
}

Can bobicator be substituted with any supertype of object ? bobicator可以用任何超类型的object代替吗? Clearly not. 显然不是。 But it just as clearly isn't a problem of the poor Frobinate type - it's an error in the IsFrob function. 但它显然不是可怜的Frobinate类型的问题 - 它是IsFrob函数中的错误。 In practice, a lot of code in C# (and most other languages) only works with objects far more constrained than would be indicated by the type in the method signature. 在实践中,C#(和大多数其他语言)中的许多代码仅适用于比方法签名中的类型所指示的约束更多的对象。

An object only violates the LSP if it violates the contract of its supertype. 如果对象违反了其超类型的合同,则该对象仅违反LSP。 It cannot be responsible for other code violationg LSP. 它不能对其他代码违反LSP负责。 And often you'll find it quite pragmatic to make code that doesn't perfectly hold up under LSP - engineering is, and always has been, about trade-offs. 通常你会发现在LSP下完全没有完全支持的代码是非常务实的 - 工程是,而且一直都是关于权衡。 Weigh the costs carefuly. 仔细权衡成本。

No, as it's not a class - the relationship between an interface and the implementing class is not the same as the relationship between super and subclass. 不,因为它不是一个类 - 接口和实现类之间的关系与super和subclass之间的关系不同。

LSP specifically applies to behaviour of code which implies implementation - an interface has no implementation so LSP does not apply. LSP特别适用于暗示实现的代码行为 - 接口没有实现,因此LSP不适用。

It is, however, a violation of the Interface Segregation Principle which says that you should rather compose interfaces to avoid unimplemented methods. 但是,这违反了接口隔离原则 ,它说您应该编写接口以避免未实现的方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM