很简单。我正在实现一个接口,但是该类没有一个属性是必需的,实际上,不应使用该属性。我最初的想法是做一些类似的事情:
int IFoo.Bar
{
get { raise new NotImplementedException(); }
}
我想这本身没有错,但是感觉并不“正确”。有人遇到过类似情况吗?如果是这样,您是如何处理的?
很简单。我正在实现一个接口,但是该类没有一个属性是必需的,实际上,不应使用该属性。我最初的想法是做一些类似的事情:
int IFoo.Bar
{
get { raise new NotImplementedException(); }
}
我想这本身没有错,但是感觉并不“正确”。有人遇到过类似情况吗?如果是这样,您是如何处理的?
Answers:
这是人们决定违反Liskov替代原则的经典示例。我强烈不建议这样做,但会鼓励采取其他解决方案:
如果第一种情况适合您,则不要在该类上实现接口。可以将其想象成一个电源插座,这里不需要接地孔,因此它实际上并没有接地。您无需插任何东西,也没什么大不了的!但是,一旦您使用需要基础的东西,您可能会遭受重大失败。最好不要打假假的孔。因此,如果您的类实际上没有执行接口所要执行的操作,请不要实现该接口。
以下是维基百科的一些快速信息:
里斯科夫换人原则可以简单地表述为:“不加强先决条件,不削弱后置条件”。
更正式地讲,Liskov替换原理(LSP)是子类型关系的一种特殊定义,称为(强)行为子类型,最初由Barbara Liskov在1987年的大会主题演讲中引入,主题为数据抽象和层次结构。它是一种语义关系,而不仅仅是句法关系,因为它旨在保证层次结构中类型的语义互操作性,[...]
为了实现相同合同的不同实现之间的语义互操作性和可替代性-您需要它们全部致力于相同的行为。
接口隔离原则提出了这样的想法,即应该将接口分离为内聚的集合,这样,当您只需要一个功能时,就不需要一个接口来完成许多不同的事情。再想一想电插座的接口,它也可以有一个恒温器,但是这会使安装电插座变得更加困难,并且可能使其更难以用于非加热目的。就像带有恒温器的电源插座一样,大型接口难以实现且难以使用。
接口隔离原则(ISP)指出,不应强迫任何客户端依赖其不使用的方法。[1] ISP将非常大的接口拆分为更小和更具体的接口,以便客户端仅需了解他们感兴趣的方法。
如果您的情况对我来说很好。
但是,在我看来,如果派生类实际上并未实现所有接口,则您的接口(或接口的使用)将被破坏。考虑拆分该接口。
免责声明:这需要多重继承才能正确执行,我不知道C#是否支持。
我遇到过这种情况。实际上,正如在其他地方所指出的那样,BCL就有这样的实例...我将尝试提供更好的示例并提供一些理由:
当您已经有接口时,出于兼容性原因而保留并...
该界面包含过时或不合时宜的成员。例如BlockingCollection<T>.ICollection.SyncRoot
(除其他外)尽管ICollection.SyncRoot
本身还不是过时的,但它会抛出NotSupportedException
。
该接口包含已记录为可选的成员,并且该实现可能会引发异常。例如在MSDN上IEnumerator.Reset
它说:
提供了Reset方法以实现COM互操作性。它不一定需要实施;相反,实现者可以简单地抛出NotSupportedException。
由于接口设计的错误,它本来应该是多个接口。BCL中常见的模式是使用来实现容器的只读版本NotSupportedException
。我自己做了,这是现在所期望的...我得到ICollection<T>.IsReadOnly
回报,true
所以你可以告诉他们。正确的设计应该是拥有接口的可读版本,然后完整的接口将从该版本继承。
没有更好的界面可以使用。例如,我有一个类,允许您按索引访问项目,检查它是否包含项目以及在什么索引上,它有一定的大小,您可以将其复制到数组中……这似乎是一项工作,IList<T>
但我的班级却固定大小,并且不支持添加或删除,因此它更像数组而不是列表。但是BCL中没有IArray<T>
。
该接口属于已移植到多个平台的API,在特定平台的实现中,不支持该接口的某些部分。理想情况下,应该有某种方法可以检测到它,以便使用此类API的可移植代码可以决定是否调用这些部分……但是,如果您调用它们,则完全合适NotSupportedException
。如果这是原始设计中未预见到的新平台的移植,则尤其如此。
还考虑为什么不支持它?
有时InvalidOperationException
是更好的选择。例如,在类中添加多态性的另一种方法是通过内部接口的各种实现,您的代码根据类的构造函数中给出的参数选择要实例化的接口。[如果您知道选项集是固定的,并且您不想允许依赖项注入引入第三方类,这特别有用。]我这样做是为了反向移植ThreadLocal,因为跟踪和非跟踪实现都是距离太远了,ThreadLocal.Values
非跟踪实施又会带来什么?InvalidOperationException
即使如此,它也不取决于对象的状态。在这种情况下,我自己介绍了该类,并且我知道必须通过抛出异常来实现此方法。
有时默认值很有意义。例如,在ICollection<T>.IsReadOnly
上面提到的情况下,根据情况仅返回“ true”或“ false”是有意义的。那么...的语义是IFoo.Bar
什么?可能会返回一些合理的默认值。
附录:如果您可以控制该接口(并且不必为了兼容而一直使用它),那么就不必抛出该异常NotSupportedException
。虽然,您可能必须将接口拆分为两个或更多个较小的接口才能适合您的情况,这在极端情况下可能会导致“污染”。
有人遇到过类似情况吗?
是的,.Net库设计师做了。Dictionary类可以完成您的工作。它使用显式实现来有效地隐藏某些IDictionary方法。在这里对其进行了更好的解释,但总而言之,为了使用带KeyValuePairs的Dictionary的Add,CopyTo或Remove方法,您首先必须将对象转换为IDictionary。
* 从严格意义上讲,这并不是“隐藏”方法,因为Microsoft使用了 “隐藏”一词。但在这种情况下,我不知道有一个更好的词。
您总是可以实现并返回良性值或默认值。毕竟这只是一个财产。它可以返回0(int的默认属性),或在实现中有意义的任何值(int.MinValue,int.MaxValue,1、42等)。
//We don't officially implement this property
int IFoo.Bar
{
{ get; }
}
引发异常似乎是不好的形式。