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

当前回答

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

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

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

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

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

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

其他回答

这么多答案! 尽我所能。呵呵。

首先,你可以使用一个具体的基类和派生类。在这种情况下,你将不得不在基类中为Prepare方法做一个空的或无用的实现,同时使这个方法为虚的,然后派生类将为自己重写这个Prepare方法。在这种情况下,在基类中实现Prepare是无用的。

选择使用接口的原因是必须定义契约,而不是实现。

有一个IPizza类型,它提供了一个功能来准备。这是合同。它是如何准备的是实现,这不是你的把关。这是各种Pizza实现的责任。 这里首选接口或抽象类而不是具体基类,因为您必须创建一个抽象,即Prepare方法。不能在具体基类中创建抽象方法。

现在你可能会说,为什么不用抽象类呢?

所以,当你需要实现100%的抽象时,你需要使用接口。但是当你需要一些抽象和具体的实现时,选择抽象类。它的意思。

例子:假设你所有的披萨都有底料,底料的准备过程是相同的。然而,所有披萨的种类和配料都有所不同。在这种情况下,您可以使用一个抽象方法Prepare和一个具体方法PreparePizzaBase创建一个抽象类。

public abstract class Pizza{
    // concrete method which is common to all pizzas.
    public PizzaBase PreparePizzaBase(){
        // code for pizza base preparation.
    }
    public abstract void Prepare();
}

public class DeluxePizza: Pizza{
    public void Prepare(){
        var base=PreparePizzaBase();
        // prepare deluxe pizza on pizza base.
    }
}

接口定义了某种功能的提供者和相应的使用者之间的契约。它将实现与契约(接口)解耦。您应该了解一下面向对象的体系结构和设计。你可能想从维基百科开始:http://en.wikipedia.org/wiki/Interface_(computing)

考虑接口的最简单方法是认识继承的意义。如果类CC继承了类C,这意味着:

类CC可以使用类C的任何public或protected成员,就像它们是自己的一样,因此只需要实现父类中不存在的东西。 对CC的引用可以传递或分配给期望对C的引用的例程或变量。

遗传的这两个功能在某种意义上是相互独立的;虽然继承同时应用这两个,但也可以应用第二个而不应用第一个。这很有用,因为允许一个对象从两个或多个不相关的类继承成员要比允许一种类型可以替代多种类型复杂得多。

接口有点像抽象基类,但有一个关键的区别:继承基类的对象不能继承任何其他类。相反,一个对象可以实现一个接口,而不影响它继承任何所需类或实现任何其他接口的能力。

One nice feature of this (underutilized in the .net framework, IMHO) is that they make it possible to indicate declaratively the things an object can do. Some objects, for example, will want data-source object from which they can retrieve things by index (as is possible with a List), but they won't need to store anything there. Other routines will need a data-depository object where they can store things not by index (as with Collection.Add), but they won't need to read anything back. Some data types will allow access by index, but won't allow writing; others will allow writing, but won't allow access by index. Some, of course, will allow both.

If ReadableByIndex and Appendable were unrelated base classes, it would be impossible to define a type which could be passed both to things expecting a ReadableByIndex and things expecting an Appendable. One could try to mitigate this by having ReadableByIndex or Appendable derive from the other; the derived class would have to make available public members for both purposes, but warn that some public members might not actually work. Some of Microsoft's classes and interfaces do that, but that's rather icky. A cleaner approach is to have interfaces for the different purposes, and then have objects implement interfaces for the things they can actually do. If one had an interface IReadableByIndex and another interface IAppendable, classes which could do one or the other could implement the appropriate interfaces for the things they can do.

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

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

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

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

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

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

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

关键是接口代表一个契约。任何实现类都必须拥有的一组公共方法。从技术上讲,接口只控制语法,即有什么方法,它们得到什么参数以及它们返回什么。通常它们也封装语义,尽管只是通过文档。

然后,您可以拥有一个接口的不同实现,并随意交换它们。在您的示例中,由于每个披萨实例都是IPizza,因此在处理未知披萨类型实例的任何地方都可以使用IPizza。任何类型继承自IPizza的实例都保证是可排序的,因为它有一个Order()方法。

Python is not statically-typed, therefore types are kept and looked up at runtime. So you can try calling an Order() method on any object. The runtime is happy as long as the object has such a method and probably just shrugs and says »Meh.« if it doesn't. Not so in C#. The compiler is responsible for making the correct calls and if it just has some random object the compiler doesn't know yet whether the instance during runtime will have that method. From the compiler's point of view it's invalid since it cannot verify it. (You can do such things with reflection or the dynamic keyword, but that's going a bit far right now, I guess.)

还要注意,通常意义上的接口不一定是c#接口,它也可以是一个抽象类,甚至是一个普通类(如果所有子类都需要共享一些公共代码,这可以派上用场——然而,在大多数情况下,接口就足够了)。