我真的不明白接口存在的原因。据我所知,这是c#中不存在的多继承的一种工作(至少我是这么被告知的)。

我所看到的是,您预定义了一些成员和函数,然后必须在类中再次重新定义它们。从而使接口成为冗余。它只是感觉像句法……嗯,垃圾对我来说(请没有冒犯的意思。Junk是指无用的东西)。

在下面的例子中,我将创建一个名为Pizza的基类,而不是一个接口。

简单示例(取自不同的堆栈溢出贡献)

public interface IPizza
{
    public void Order();
}

public class PepperoniPizza : IPizza
{
    public void Order()
    {
        //Order Pepperoni pizza
    }
}

public class HawaiiPizza : IPizza
{
    public void Order()
    {
        //Order HawaiiPizza
    }
}

当前回答

在Python中没有鸭子类型的情况下,c#依赖接口来提供抽象。如果类的依赖项都是具体类型,则不能传入任何其他使用类型的接口,可以传入实现该接口的任何类型。

其他回答

Interface =契约,用于松耦合(参见GRASP)。

以下是你的例子:

public interface IFood // not Pizza
{
    public void Prepare();

}

public class Pizza : IFood
{
    public void Prepare() // Not order for explanations sake
    {
        //Prepare Pizza
    }
}

public class Burger : IFood
{
    public void Prepare()
    {
        //Prepare Burger
    }
}

对我来说,刚开始的时候,只有当你不再把它们看作是让你的代码更容易/更快编写的东西时,它们的意义才变得清晰——这不是它们的目的。它们有很多用途:

(这里就没有披萨的比喻了,因为这个比喻的用法不太容易想象)

假设你正在屏幕上制作一款简单的游戏,游戏中会有与你互动的生物。

答:通过在前端和后端实现之间引入松散耦合,它们可以使您的代码在将来更容易维护。

你可以这样写,因为这里只会有喷子:

// This is our back-end implementation of a troll
class Troll
{
    void Walk(int distance)
    {
        //Implementation here
    }
}

前端:

function SpawnCreature()
{
    Troll aTroll = new Troll();
    
    aTroll.Walk(1);
}

两周后,市场营销决定你也需要半兽人,因为他们在twitter上看到了他们,所以你必须做如下事情:

class Orc
{
    void Walk(int distance)
    {
        //Implementation (orcs are faster than trolls)
    }
}

前端:

void SpawnCreature(creatureType)
{
    switch(creatureType)
    {
         case Orc:

           Orc anOrc = new Orc();
           anORc.Walk();

          case Troll:

            Troll aTroll = new Troll();
             aTroll.Walk();
    }
}

你可以看到这是如何变得混乱的。你可以在这里使用一个接口,这样你的前端就会被编写一次(这里是重要的部分)测试,然后你可以根据需要插入更多的后端项目:

interface ICreature
{
    void Walk(int distance)
}

public class Troll : ICreature
public class Orc : ICreature 

//etc

前端则为:

void SpawnCreature(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();
}

前端现在只关心接口ICreature -它不关心喷子或兽人的内部实现,而只关心他们实现ICreature的事实。

从这个角度来看,需要注意的一点是,您也可以很容易地使用抽象生物类,从这个角度来看,这具有相同的效果。

你可以将创建的内容提取到工厂:

public class CreatureFactory {

 public ICreature GetCreature(creatureType)
 {
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    return creature;
  }
}

我们的前端会变成:

CreatureFactory _factory;

void SpawnCreature(creatureType)
{
    ICreature creature = _factory.GetCreature(creatureType);

    creature.Walk();
}

现在,前端甚至不需要有实现Troll和Orc的库的引用(假设工厂在一个单独的库中)——它不需要知道任何关于它们的信息。

B:假设你拥有在你的同质数据结构中只有某些生物才有的功能,例如:

interface ICanTurnToStone
{
   void TurnToStone();
}

public class Troll: ICreature, ICanTurnToStone

前端可以是:

void SpawnCreatureInSunlight(creatureType)
{
    ICreature creature = _factory.GetCreature(creatureType);

    creature.Walk();

    if (creature is ICanTurnToStone)
    {
       (ICanTurnToStone)creature.TurnToStone();
    }
}

C:依赖注入用法

大多数依赖注入框架在前端代码和后端实现之间存在非常松散的耦合时才能工作。如果我们以上面的工厂为例,让我们的工厂实现一个接口:

public interface ICreatureFactory {
     ICreature GetCreature(string creatureType);
}

我们的前端可以通过构造函数注入(例如MVC API控制器)(通常):

public class CreatureController : Controller {

   private readonly ICreatureFactory _factory;

   public CreatureController(ICreatureFactory factory) {
     _factory = factory;
   }

   public HttpResponseMessage TurnToStone(string creatureType) {

       ICreature creature = _factory.GetCreature(creatureType);
   
       creature.TurnToStone();

       return Request.CreateResponse(HttpStatusCode.OK);
   }
}

使用我们的DI框架(例如Ninject或Autofac),我们可以设置它们,以便在运行时在构造函数中需要ICreatureFactory时创建一个CreatureFactory实例——这使我们的代码美观而简单。

这也意味着当我们为控制器编写单元测试时,我们可以提供一个模拟的ICreatureFactory(例如,如果具体实现需要访问DB,我们不希望我们的单元测试依赖于它),并轻松地测试控制器中的代码。

D:还有其他用途,例如,你有两个项目A和B,由于“遗留”原因,它们没有很好地组织起来,而A有B的参考。

然后在B中发现需要调用a中已经存在的方法的功能。由于您获得的是循环引用,因此不能使用具体实现来实现。

你可以在B中声明一个接口,然后由A中的类实现。你在B中的方法可以被传递一个实现接口的类的实例,即使具体对象是a中的类型。

接口用于应用不同类之间的连接。例如,你有一个关于汽车和树的类;

public class Car { ... }

public class Tree { ... }

您希望为这两个类添加一个可燃功能。但是每个职业都有自己的燃烧方式。所以你只要做;

public class Car : IBurnable
{
public void Burn() { ... }
}

public class Tree : IBurnable
{
public void Burn() { ... }
}

考虑一下不控制或不拥有基类的情况。

以可视化控件为例,在。net for Winforms中,它们都继承自。net框架中完全定义的基类Control。

让我们假设您从事创建自定义控件的业务。你想要建立新的按钮,文本框,列表视图,网格,等等,你希望他们都有特定的功能独特的控件集。

例如,你可能想要一种通用的方法来处理主题,或者一种通用的方法来处理本地化。

在这种情况下,你不能“只创建一个基类”,因为如果你这样做,你必须重新实现所有与控件相关的东西。

相反,您将从按钮,TextBox, ListView, GridView等下降,并添加您的代码。

但这就产生了一个问题,你现在如何识别哪些控件是“你的”,你如何构建一些代码来表明“对于窗体上所有属于我的控件,将主题设置为X”。

输入接口。

接口是一种查看对象、确定对象是否遵守某种约定的方法。

您可以创建“YourButton”,从Button向下延伸,并添加对所需的所有接口的支持。

这将允许您编写如下代码:

foreach (Control ctrl in Controls)
{
    if (ctrl is IMyThemableControl)
        ((IMyThemableControl)ctrl).SetTheme(newTheme);
}

如果没有接口,这是不可能的,相反,你必须写这样的代码:

foreach (Control ctrl in Controls)
{
    if (ctrl is MyThemableButton)
        ((MyThemableButton)ctrl).SetTheme(newTheme);
    else if (ctrl is MyThemableTextBox)
        ((MyThemableTextBox)ctrl).SetTheme(newTheme);
    else if (ctrl is MyThemableGridView)
        ((MyThemableGridView)ctrl).SetTheme(newTheme);
    else ....
}