接口继承:你怎么看待这个:

时间:2008-11-21 08:09:37

标签: c# oop inheritance liskov-substitution-principle

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

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

interface IInterface2 : IBase
{
    void Method3();
}

class Class1 : IInterface2
{
    ...
}

class Class2 : IInterface2
{
    ...
}

class Class3 : IInterface2
{ 
    ...
}

Class2Method1投掷NotImplementedException

问题:

  • 您对继承接口的看法如何?
  • IBaseClass2之间的关系是否违反Liskov替代原则?

6 个答案:

答案 0 :(得分:10)

嗯,首先,我一般都反对通过抛出NotImplementedException异常来实现接口。它基本上就像是说“嗯,这个类也可以作为一个计算器,错误,差不多”。

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

需要注意的事项。

接口是合同,通过实施您说您遵守合同的界面。如果你开始否定合同的某些部分,那么对我而言,合同或其实施的结果都很难以理解。


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


至于替代原则,states表示:

  

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

在这种情况下,如果改变方法在子代类型中的作用,它就违反了从基类重写方法的原则。

原则在维基百科页面上更详细,如下列几点(括号和对我的评论的重点):

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

由于你没有显示你的接口的完整契约,只有编译器可以检查的声明部分,所以不可能知道原则适用于你的实现。

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

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

然后抛出NotImplementedException违反了原则。

但是,如果合同还声明:

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

然后它可能没有。

答案 1 :(得分:4)

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

  

如果派生类型抛出a   NotImplementedException表示方法   基本类型没有的地方,这样做   违反Liskov的替换   原理

我会说这取决于接口文档是否说某个方法可能会抛出此异常以履行其合同。如果是这样,那么它不会违反原则,否则就会违反原则。

.NET框架中的典型示例是Stream类,其中包含ReadWriteSeek等一系列操作,但没有要求对于流来支持所有这些操作,并记录为允许抛出NotSupportedException

答案 2 :(得分:1)

假设我理解你的意思,那么我认为答案是肯定的,继承接口是好的,不,它不违反Liskov替换原则。

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

答案 3 :(得分:1)

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

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

interface ISubmergible
{
  void Submerge();
}

很明显,如果类实现了接口,那么它就可以淹没。但是,某些接口意味着其他接口,例如,想象一下这个接口

interface IRunner
{
  void Run();
}

它定义了一个接口,指示实现它的类可以运行...尽管如此,在我们的程序的上下文中,可以理解,如果某些东西可以运行它显然可以走路,所以你想确保它满足:

interface IWalker
{
  void Walk();
}

interface IRunner : IWalker
{
  void Run();
}

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

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

答案 4 :(得分:1)

抛出NotSupportedException是正常的:当设计没有实现某些接口功能时,这是抛出的异常。

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

有关此问题的讨论,请参阅Brad Abram's blog

答案 5 :(得分:0)

接口中的继承用于.Net框架中的几个位置。所以虽然不是那里的所有东西都是完美的,但我认为这是好的。例如,查看IEnumerable接口。

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