当不需要属性之一时实现接口


31

很简单。我正在实现一个接口,但是该类没有一个属性是必需的,实际上,不应使用该属性。我最初的想法是做一些类似的事情:

int IFoo.Bar
{
    get { raise new NotImplementedException(); }
}

我想这本身没有错,但是感觉并不“正确”。有人遇到过类似情况吗?如果是这样,您是如何处理的?


1
我隐约记得在C#中有一些半常用的类实现了一个接口,但是在文档中明确指出未实现某种方法。我将尝试看看是否可以找到它。
法师Xy

如果您能找到它,我肯定会很感兴趣。
克里斯·普拉特

23
我可以在.NET的库中指出这种情况的多种情况- 并且它们都被识别为严重的错误。这是对Liskov换位原则的标志性且普遍的违反- 违反LSP的原因可以在我的答案中找到
Jimmy Hoffa,2015年

3
您是否需要实现此特定接口,还是可以引入一个超级接口并使用它?
Christopher Schultz,2015年

6
“该类没有必要的一个属性”-接口的一部分是否必要取决于接口的客户端,而不是实现者。如果一个类不能合理地实现接口的成员,则该类不适合该接口。这可能意味着接口设计不当-可能尝试做太多事情-但这对类没有帮助。
塞巴斯蒂安·雷德尔

Answers:


51

这是人们决定违反Liskov替代原则的经典示例。我强烈不建议这样做,但会鼓励采取其他解决方案:

  1. 如果您正在编写的类没有使用该接口的所有成员,则可能不提供该接口规定的功能。
  2. 或者,该接口可能会做很多事情,并且可以按照接口隔离原则将其分开。

如果第一种情况适合您,则不要在该类上实现接口。可以将其想象成一个电源插座,这里不需要接地孔,因此它实际上并没有接地。您无需插任何东西,也没什么大不了的!但是,一旦您使用需要基础的东西,您可能会遭受重大失败。最好不要打假假的孔。因此,如果您的类实际上没有执行接口所要执行的操作,请不要实现该接口。


以下是维基百科的一些快速信息:

里斯科夫换人原则可以简单地表述为:“不加强先决条件,不削弱后置条件”。

更正式地讲,Liskov替换原理(LSP)是子类型关系的一种特殊定义,称为(强)行为子类型,最初由Barbara Liskov在1987年的大会主题演讲中引入,主题为数据抽象和层次结构。它是一种语义关系,而不仅仅是句法关系,因为它旨在保证层次结构中类型的语义互操作性,[...]

为了实现相同合同的不同实现之间的语义互操作性和可替代性-您需要它们全部致力于相同的行为。


接口隔离原则提出了这样的想法,即应该将接口分离为内聚的集合,这样,当您只需要一个功能时,就不需要一个接口来完成许多不同的事情。再想一想电插座的接口,它也可以有一个恒温器,但是这会使安装电插座变得更加困难,并且可能使其更难以用于非加热目的。就像带有恒温器的电源插座一样,大型接口难以实现且难以使用。

接口隔离原则(ISP)指出,不应强迫任何客户端依赖其不使用的方法。[1] ISP将非常大的接口拆分为更小和更具体的接口,以便客户端仅需了解他们感兴趣的方法。


绝对。首先,这可能就是为什么它不适合我的原因。有时,您只需要轻轻地提醒您您正在做一些愚蠢的事情。谢谢。
克里斯·普拉特

@ChrisPratt这是一个非常普遍的错误,这就是形式主义很有价值的原因-对代码气味进行分类有助于更快地识别它们并调用以前使用的解决方案。
Jimmy Hoffa

4

如果您的情况对我来说很好。

但是,在我看来,如果派生类实际上并未实现所有接口,则您的接口(或接口的使用)将被破坏。考虑拆分该接口。

免责声明:这需要多重继承才能正确执行,我不知道C#是否支持。


6
C#不支持类的多重继承,但确实支持接口。
mgw854

2
我觉得你是对的。该接口毕竟是一个契约,即使我知道这个特定的类也不会以某种方式使用,如果禁用了此属性,它将破坏使用该接口的任何东西,这并不是显而易见的。
克里斯·普拉特


4

我遇到过这种情况。实际上,正如在其他地方所指出的那样,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。虽然,您可能必须将接口拆分为两个或更多个较小的接口才能适合您的情况,这在极端情况下可能会导致“污染”。


0

有人遇到过类似情况吗?

是的,.Net库设计师做了。Dictionary类可以完成您的工作。它使用显式实现来有效地隐藏某些IDictionary方法。在这里对其进行了更好解释,但总而言之,为了使用带KeyValuePairs的Dictionary的Add,CopyTo或Remove方法,您首先必须将对象转换为IDictionary。

* 从严格意义上讲,这并不是“隐藏”方法,因为Microsoft使用了 “隐藏”一词。但在这种情况下,我不知道有一个更好的词。



为什么要下票?
user2023861 2015年

2
可能是因为您没有回答问题。伊什 有点。
与莫妮卡(Monica)进行的轻度比赛

@LightnessRacesinOrbit,我更新了答案以使其更加清晰。希望对OP有帮助。
user2023861 2015年

2
好像它回答了我的问题。这可能是一个好主意还是坏事,这似乎不在问题的范围之内,但对于指示答案仍然有用。
Ellesedil 2015年

-6

您总是可以实现并返回良性值或默认值。毕竟这只是一个财产。它可以返回0(int的默认属性),或在实现中有意义的任何值(int.MinValue,int.MaxValue,1、42等)。

//We don't officially implement this property
int IFoo.Bar
{
     { get; }
}

引发异常似乎是不好的形式。


2
为什么?如何更好地返回不正确的数据?
与莫妮卡(Monica)进行的轻度比赛

1
这会破坏LSP:请参阅Jimmy Hoffa的答案以获取原因解释。

5
如果没有正确的返回值,则抛出异常要比返回不正确的值更好。无论您做什么,如果程序意外调用此属性,都将导致其故障。但是,如果引发异常,那么很明显为什么会出现故障。
Tanner Swett 2015年

我现在不知道当您实现接口时,该值怎么会不正确!它是您的实现,可以是您想要的任何东西。
乔恩·雷诺

如果编译时未知正确的值怎么办?
安迪
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.