Liskov替换原理和接口

时间:2017-05-18 13:48:19

标签: c# interface liskov-substitution-principle

ICollection<T>.Add() - 数组的实现是否会破坏Liskov替换原则?该方法导致NotSupportedException,它确实破坏了LSP,恕我直言。

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

这导致

  

未处理的异常:System.NotSupportedException:Collection是   一个固定的大小。

我发现了一个关于Stream - 实现的非常类似的问题。我打开一个单独的问题,因为这个案例非常不同:Liskov substitution principle and Streams。 这里的不同之处在于,ICollection不提供CanAdd - 属性或此类内容,正如Stream - 类所做的那样。

2 个答案:

答案 0 :(得分:6)

我明白为什么你这么认为。有一个函数需要一个集合,它期望它是可修改的。传递一个数组会使它失败,所以显然你不能用这个特定的实现替换接口,对吗?

这是一个问题吗?也许。这取决于你期望理想持有的频率。你是不是偶然会使用一个阵列而不是一个集合,然后在十年之后惊讶它会崩溃?并不是的。 .NET应用程序使用的类型系统并不完美 - 它没有告诉您这种特定的ICollection<T>用法要求集合可以修改。

如果数组没有假装实现ICollection<T>(或IEnumerable<T>,而他们也没有“真正”实现),.NET会更好吗?我不这么认为。有没有办法保持数组“正在”ICollection<T>的便利性,这也可以避免相同的LSP违规?不。底层数组仍然是固定长度的 - 充其量,你会违反更多有用的原则(比如参考类型不具备引用透明性这一事实)。

但是等等!让我们看看ICollection<T>.Add的实际合同。是否允许抛出NotSupportedException?哦是的 - 引用MSDN:

  

[如果......则抛出NotSupportedException] ICollection是只读的。

当您查询IsReadOnly时,数组会返回true。合同得到维护。

如果您认为StreamCanWrite而无法中断LSP,则必须将数组视为有效集合,因为它们具有IsReadOnly,并且它是true。如果函数接受只读集合并尝试添加它,则该函数中存在错误。在C#/ .NET中无法明确指定这一点,因此您必须依赖合同的其他部分而不仅仅是类型 - 例如该函数的文档应指定为只读的集合抛出NotSupportedException(或ArgumentException或其他)。一个好的实现将在函数开始时进行此测试。

需要注意的一件重要事情是,C#中的类型并不像定义LSP的类型理论那样受到限制。例如,您可以在C#中编写这样的函数:

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

可以用bobicator的任何超类型替换object吗?显然不是。但它显然不是穷人Frobinate类型的问题 - 它是IsFrob函数中的错误。实际上,C#(以及大多数其他语言)中的许多代码仅适用于比方法签名中的类型所指示的约束更多的对象。

如果某个对象违反了其超类型的合同,则该对象仅违反该LSP。它不能对其他代码违规LSP负责。通常,你会发现在LSP下完成完美的代码是非常务实的 - 工程是,而且一直都是关于权衡。仔细权衡成本。

答案 1 :(得分:1)

不,因为它不是一个类 - 接口和实现类之间的关系与super和subclass之间的关系不同。

LSP特别适用于暗示实现的代码行为 - 接口没有实现,因此LSP不适用。

然而,这违反了接口隔离原则,它表示您应该编写接口以避免未实现的方法。