我真的不明白接口存在的原因。据我所知,这是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
    }
}

当前回答

你会得到界面,当你需要他们:)你可以研究例子,但你需要的是啊哈!效果才能真正得到他们。

现在您已经知道了接口是什么,只需编写没有接口的代码。您迟早会遇到一个问题,在这个问题中使用接口将是最自然的事情。

其他回答

如果我正在使用一个API来绘制形状,我可能想使用DirectX或图形调用,或OpenGL。因此,我将创建一个接口,它将从您调用的内容中抽象出我的实现。

所以你调用一个工厂方法:MyInterface i = MyGraphics.getInstance()。然后,你有一个契约,所以你知道你可以在MyInterface中期望什么功能。你可以调用i。drawrectangle或i。drawcube并且知道如果你把一个库换成另一个库,函数是被支持的。

如果您正在使用依赖注入,这就变得更加重要,因为您可以在XML文件中交换实现。

所以,你可能有一个加密库可以导出,供一般使用,而另一个加密库只出售给美国公司,区别在于你改变了配置文件,而程序的其余部分不会改变。

这在。net中的集合中被大量使用,就像你应该只使用,例如,列表变量,不要担心它是一个数组列表还是LinkedList。

只要您编写了接口代码,那么开发人员就可以更改实际的实现,而程序的其余部分则保持不变。

这在单元测试时也很有用,因为您可以模拟出整个接口,因此,我不需要访问数据库,而是只返回静态数据的模拟出来的实现,因此我可以测试我的方法,而不用担心数据库是否需要维护。

什么?

接口基本上是一个契约,所有实现接口的类都应该遵循这个契约。它们看起来像一个类,但没有实现。

在c#中,接口名称的约定是由前缀“I”定义的,所以如果你想要一个名为shapes的接口,你可以将它声明为IShapes

为什么?

提高代码的可重用性

假设你想画圆,三角形。 你可以把它们组合在一起,称为形状,并有方法来绘制圆形和三角形 但有具体的实现将是一个坏主意,因为明天你可能会决定有2个形状矩形和正方形。现在,当您添加它们时,很可能会破坏代码的其他部分。

使用Interface,您可以将不同的实现与契约隔离开来


现场场景第一天

你被要求创建一个应用程序来绘制圆形和三角形

interface IShapes
{
   void DrawShape();
   
 }

class Circle : IShapes
{
    
    public void DrawShape()
    {
        Console.WriteLine("Implementation to Draw a Circle");
    }
}

Class Triangle: IShapes
{
     public void DrawShape()
    {
        Console.WriteLine("Implementation to draw a Triangle");
    }
}
static void Main()
{
     List <IShapes> shapes = new List<IShapes>();
        shapes.Add(new Circle());
        shapes.Add(new Triangle());

        foreach(var shape in shapes)
        {
            shape.DrawShape();
        }
}

现场场景第二天

如果你被要求添加Square和Rectangle,你所要做的就是在Square: IShapes类中创建它的实现,并在Main中添加到列表形状。添加(新广场());

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

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

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

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

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

// 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中的类型。

我知道我已经迟到了。(差不多九年了),但如果有人想要简单的解释,你可以这样说:

简单地说,当你知道一个对象可以做什么,或者我们要在一个对象上实现什么函数时,你就使用接口。使用实例插入、更新和删除。

interface ICRUD{
      void InsertData(); // will insert data
      void UpdateData(); // will update data
      void DeleteData(); // will delete data
}

重要提示:接口总是公共的。

希望这能有所帮助。

在这种情况下,您可以(也可能会)定义一个Pizza基类并从它们继承。然而,接口允许你做一些其他方式无法做到的事情有两个原因:

一个类可以实现多个接口。它只是定义类必须具有的特性。实现一系列接口意味着一个类可以在不同的地方实现多种功能。 接口可以定义在比类或调用方更大的范围内。这意味着您可以分离功能,分离项目依赖项,并将功能保留在一个项目或类中,并在其他地方实现该功能。

2的一个含义是,您可以更改正在使用的类,只需要它实现适当的接口。