26

Let's say that I have an abstract base class something simple like

abstract class Item : IDisplayable
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public abstract void Print();

    }

and I have a class that inherits from that like

  class Chair: Item
 {
    public int NumberOfLegs {get;set;}

    public void Print()
    {
     Console.WriteLine("Here is a simple interface implementation");
    }
 }

interface IDisplayable
{
void Print();
}

the child class does not explicitly say that it also implements the Interface, and yet it will do so through simple inheritance. If we explicitly add the Interface to the child classes the program will run the same (at least as far as I can tell in my simple examples). Would explicitly implementing the interface be a good or bad idea, or is it strictly a matter of preference?

4

3 回答 3

46

如果我们将接口显式添加到子类中,程序将运行相同(至少就我在简单示例中所知道的而言)。

该程序不一定会运行相同的;你的例子不足以说明差异。

显式实现接口是一个好主意还是坏主意,还是严格来说是一个偏好问题?

除非您打算确保接口重新实现语义,否则这是一个坏主意。

让我简要说明一下。这个程序有什么作用?

using System;
interface IFoo { void Bar(); void Baz(); }
class Alpha : IFoo
{ 
    void IFoo.Bar() 
    {
        Console.WriteLine("Alpha.Bar");
    }
    void IFoo.Baz()
    {
        Console.WriteLine("Alpha.Baz");
    }
}
class Bravo : Alpha
{
    public void Baz()
    {
        Console.WriteLine("Bravo.Baz");
    }
}
class CharlieOne : Bravo
{
    public void Bar() 
    {
        Console.WriteLine("CharlieOne.Bar");
    }
}
class CharlieTwo : Bravo, IFoo
{
    public void Bar() 
    {
        Console.WriteLine("CharlieTwo.Bar");
    }
} 
class Program
{
    static void Main()
    {
        IFoo foo = new Alpha();
        foo.Bar();
        foo.Baz();
        foo = new Bravo();
        foo.Bar();
        foo.Baz();
        foo = new CharlieOne();
        foo.Bar();
        foo.Baz();
        foo = new CharlieTwo();
        foo.Bar();
        foo.Baz();
     }
}

在您继续阅读之前,请认真:尝试预测该程序的输出

现在实际运行它。你得到你期望的输出了吗?你的直觉哪里错了?

你看出CharlieOneCharlieTwo现在的区别了吗? 重新实现IFooinCharlieTwo可能会导致接口绑定拾取,Bravo.Baz即使Bravo没有重新实现IFoo

另一方面:如果你期望Bravo.Baz被分配到接口槽只是因为它存在,那么你会看到未能重新实现接口会导致代码不正确。为了Bravo.Baz替换Alpha.IFoo.BazBravo必须重新执行IFoo

这里的要点是:当你重新实现一个接口时,所有的接口绑定都是从头开始重新计算的。这可能会导致程序中的语义更改,因此只有在您打算这样做时才重新实现接口

这也说明了另一种形式的脆弱基类故障。假设你写的时候Bravo没有方法。如果您写信重新实现,那么之后添加的作者(也许作者在您公司的不同团队中)会更改内部的接口绑定,即使这不是作者的意图。 BazCharlieCharlieIFooBravoBaz BravoCharlieBravo

有关更多信息,请参阅我关于该主题的文章:

http://blogs.msdn.com/b/ericlippert/archive/2011/12/08/so-many-interfaces-part-two.aspx

于 2013-06-24T15:07:21.383 回答
4

既然Item继承自IDisplayable,任何派生自的东西也Item必须实现IDisplayable。因此,显式添加IDisplayable到这些类是多余的。

编辑:@EricLippert 在下面就重新实现接口的效果提出了一个很好的观点。我留下这个答案是为了保留评论中的讨论。

于 2013-06-24T14:49:03.307 回答
0

当您说“显式实现接口”时,我假设您的意思是在类声明中包含接口。显式实现接口有不同的具体含义。话虽如此,声明派生类实现基类实现的接口是不正确的,尽管编译器允许这样做,因为派生类不包含该接口的实现。考虑如果基类显式实现接口会发生什么 -

abstract class Item : IDisplayable
{
    void IDisplayable.Print() { ... }
}

现在派生类对基类方法没有可见性,派生类的消费者也没有。当然,消费者应该使用接口而不是具体派生类,然后才能访问显式接口方法。但是在派生类本身的范围内,它并不知道基类实现的接口。只是某些成员是抽象的、受保护的或虚拟的。

于 2013-06-24T15:05:09.640 回答