4

在查看我们的代码库时,我发现了一个类似于以下模式的继承结构:

interface IBase
{
    void Method1();
    void Method2();
}

interface IInterface2 : IBase
{
    void Method3();
}

class Class1 : IInterface2
{
    ...
}

class Class2 : IInterface2
{
    ...
}

class Class3 : IInterface2
{ 
    ...
}

Class2Method1投掷NotImplementedException

问题:

  • 您一般如何看待继承接口?
  • IBase之间的关系是否Class2违反了里氏替换原则?
4

6 回答 6

10

好吧,首先,我通常反对通过抛出 NotImplementedException 异常来实现接口。这基本上就像在说“嗯,这个类也可以用作计算器,错误,几乎”。

但在某些情况下,它确实是做某事“正确方式”的唯一方法,所以我不是 100% 反对它。

只是需要注意的事情。

接口就是契约,实现了接口就表示你遵守契约。如果您随后开始否定合同的某些部分,那么在我看来,合同或合同的实施是经过深思熟虑的。


编辑:在看到Greg Beech的回答之后:如果一个接口明确表示实现应该抛出这些异常,那么它是合同的一部分,那么我同意完全允许该类这样做。


至于替代原则,它指出

令 q(x) 是关于 T 类型的对象 x 的可证明性质。那么 q(y) 对于 S 类型的对象 y 应该为真,其中 S 是 T 的子类型。

在这种情况下,如果您更改该方法在后代类型中的作用,它就与从基类覆盖方法一样违反原则。

该原理在维基百科页面上更详细,例如以下几点(括号和强调我的评论):

  • 不能在子类中加强先决条件。(前提条件可能是“此时该类已准备好调用此方法”)
  • 后置条件不能在子类中被削弱。(后置条件可能是调用方法后,类的状态为真)

由于您没有显示接口的完整合同,只有编译器可以检查的声明部分,因此不可能知道该原则是否适用于您的实现。

例如,如果您的 Method2 附加了以下条件:

  • 随时可以调用
  • 修改对象的状态,为事件链中的下一个事件做好准备

然后抛出 NotImplementedException 违反了原则。

但是,如果合同还规定:

  • 对于不支持事件链的类,此方法应抛出 NotImplementedException 或 NotSupportedException

那么它可能不会。

于 2008-11-21T08:12:54.567 回答
4

所以,我假设你要问的问题是:

如果派生类型 NotImplementedException为基类型没有的方法抛出 a,这是否违反了 Liskov 替换原则。

我想说这取决于接口文档是否说一个方法可能会抛出这个异常来履行它的合同。如果是,则不违反原则,否则违反。

.NET 框架中 this 的经典示例是具有大量操作Stream(例如.ReadWriteSeekNotSupportedException

于 2008-11-21T08:28:28.297 回答
1

假设我明白你的意思,那么我认为答案是肯定的,继承接口很好,不,它不违反里氏替换原则。

考虑接口的方式是“表现得像一个”运算符。它描述了类承诺遵守的一组行为,由方法等表示。因此,从 IEatsMice 接口继承的 IBehavesLikeACat 接口没有问题:如果它的行为像猫,那么它显然会吃老鼠。所以一只猫会同时实现这两者,一只雪貂只有 IEatsMice。

于 2008-11-21T08:33:04.230 回答
1

首先,接口中的继承是可以的,它的应用方式与类继承相同,是一个非常强大的工具。

接口描述行为,假设一个接口定义了一个类“可以做什么”,所以如果你实现一个接口,你就声明你可以做那个接口指定的事情。例如:

interface ISubmergible
{
  void Submerge();
}

很明显,如果类实现了接口,那么它可以淹没。一些接口仍然暗示其他接口,例如,想象这个接口

interface IRunner
{
  void Run();
}

这定义了一个接口,表明实现它的类可以运行......但是,在我们程序的上下文中,如果有东西可以运行,它显然可以行走,所以你要确保它是满足的:

interface IWalker
{
  void Walk();
}

interface IRunner : IWalker
{
  void Run();
}

最后,关于整个 NotImplementedException 事情......我对一些建议感到惊讶。NotImplementedException永远不应该由类的接口方法引发。如果你实现了一个接口,你就明确地接受了接口建立的契约,如果你提出一个 NotImplementedException,你基本上是在说“好吧,我撒谎了,我告诉过你我支持这个接口,但我真的不支持”。

接口旨在避免担心类实现了什么,什么没有实现。如果你把它拿走,它们就没用了。更重要的是,您的同事会期待这样的行为,即使您了解自己在做什么,团队的其他成员也不会,甚至您,从现在起 6 个月后也不会理解其背后的逻辑。所以 class2 违反了常识和接口目的。Class2 没有实现 IBase,所以不要声称它实现了。

于 2008-11-21T09:18:27.603 回答
1

抛出 NotSupportedException 是可以的:这是在某些接口功能未按设计实现时抛出的异常。

NotImplementedException 不太清楚:这通常用于尚未实现但将会实现的代码。例如,Visual Studio 2008 生成的接口实现存根包含代码“throw new NotImplementedException()”。

有关此问题的讨论,请参见Brad Abram 的博客

于 2008-11-21T10:22:17.363 回答
0

.Net 框架中的多个地方使用了接口中的继承。所以虽然不是那里的所有东西都是完美的,但我想这被认为是可以的。以 IEnumerable 接口为例。

至于抛出 NotImplementedExceptions,我想说这取决于您在代码中如何使用它们。例如,您可以使用多种方法定义接口,其中一些方法是可选的。在这种情况下,您应该检查该方法是否可用,并且仅在可用时才使用它。

于 2008-11-21T08:44:54.347 回答