实现接口的抽象基类

时间:2013-06-24 14:45:06

标签: c#

假设我有一个简单的抽象基类,如

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

    }

我有一个继承自

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

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

interface IDisplayable
{
void Print();
}

子类没有明确地说它也实现了接口,但它将通过简单的继承来实现。如果我们将接口显式添加到子类,程序将运行相同的程序(至少在我的简单示例中可以告诉我)。明确地实现界面是一个好主意还是坏主意,还是严格来说是偏好?

3 个答案:

答案 0 :(得分:43)

  

如果我们明确地将接口添加到子类,程序将运行相同的程序(至少在我的简单示例中可以告诉我)。

该计划不一定会运行;你的例子不足以说明不同之处。

  

明确地实现界面是一个好主意还是坏主意,还是严格来说是偏好?

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

让我简要说明一下。这个程序做了什么?

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之间的区别吗? IFoo中重新实施CharlieTwo会导致界面绑定选择Bravo.Baz,即使Bravo 重新实现{ {1}} <!/强>

另一方面:如果您希望将IFoo分配给接口插槽只是因为它存在,那么您会看到失败如何重新实现接口导致代码不正确。要Bravo.Baz替换Bravo.BazAlpha.IFoo.Baz必须重新实施Bravo

这里要说的是:当你重新实现一个接口时,所有接口绑定从头开始重新计算 这会导致语义程序中的更改,因此仅在您打算执行时重新实现界面

这也说明了另一种形式的脆弱基类失败。假设您IFooBravo没有方法Baz。如果您撰写Charlie以重新实施Charlie,则IFoo的作者之后会添加Bravo < - em> - 也许是{{1}的作者在您公司的不同团队中 - 更改Baz内的界面绑定,即使这不是Bravo的作者所期望的。

有关详情,请参阅我关于此主题的文章:

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

答案 1 :(得分:4)

由于Item继承自IDisplayable,因此Item派生的所有内容也必须实现IDisplayable。因此,明确地将IDisplayable添加到这些类是多余的。

编辑:@EricLippert在下面重点介绍了重新实现接口的效果。我将离开这个答案以保留评论中的讨论。

答案 2 :(得分:0)

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

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

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