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

当前回答

没有人真正清楚地解释过接口是如何有用的,所以我打算尝试一下(并从Shamim的回答中窃取一点想法)。

让我们以披萨订购服务为例。您可以有多种类型的披萨,每个披萨的共同操作是在系统中准备订单。每个披萨都要准备,但每个披萨的准备方式不同。例如,当点菜时,系统可能需要验证餐厅是否有特定的食材,并将不需要做深盘披萨的食材放在一边。

当用代码写这个的时候,技术上你可以这样做

public class Pizza
{
    public void Prepare(PizzaType tp)
    {
        switch (tp)
        {
            case PizzaType.StuffedCrust:
                // prepare stuffed crust ingredients in system
                break;
                
            case PizzaType.DeepDish:
                // prepare deep dish ingredients in system
                break;
                
            //.... etc.
        }
    }
}

然而,深盘披萨(在c#术语中)可能需要在Prepare()方法中设置与填充披萨皮不同的属性,因此最终会有许多可选属性,并且类不能很好地伸缩(如果添加新的披萨类型会怎样)。

解决这个问题的正确方法是使用接口。界面声明所有披萨都可以准备,但每个披萨可以准备不同。如果你有以下接口:

public interface IPizza
{
    void Prepare();
}

public class StuffedCrustPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for stuffed crust preparations
    }
}

public class DeepDishPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for deep dish preparations
    }
}

现在,您的订单处理代码不需要确切地知道订购了什么类型的披萨来处理配料。它只有:

public PreparePizzas(IList<IPizza> pizzas)
{
    foreach (IPizza pizza in pizzas)
        pizza.Prepare();
}

尽管每种类型的披萨都是不同的,但这部分代码并不需要关心我们处理的是哪种类型的披萨,它只知道它正在为披萨调用,因此每次调用Prepare都会根据其类型自动正确地准备每个披萨,即使集合有多种类型的披萨。

其他回答

Pizza示例很糟糕,因为您应该使用一个处理排序的抽象类,而pizzas应该重写Pizza类型。

当您有一个共享属性,但是您的类从不同的地方继承,或者当您没有任何可以使用的公共代码时,您可以使用接口。例如,这是用过的东西,可以被处置为IDisposable,你知道它会被处置,你只是不知道当它被处置时会发生什么。

接口只是一个契约,它告诉你一个对象可以做一些事情,什么样的参数和期望什么样的返回类型。

考虑到你不能在c#中使用多重继承,然后再看看你的问题。

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

然后,您可以拥有一个接口的不同实现,并随意交换它们。在您的示例中,由于每个披萨实例都是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#接口,它也可以是一个抽象类,甚至是一个普通类(如果所有子类都需要共享一些公共代码,这可以派上用场——然而,在大多数情况下,接口就足够了)。

没有人真正清楚地解释过接口是如何有用的,所以我打算尝试一下(并从Shamim的回答中窃取一点想法)。

让我们以披萨订购服务为例。您可以有多种类型的披萨,每个披萨的共同操作是在系统中准备订单。每个披萨都要准备,但每个披萨的准备方式不同。例如,当点菜时,系统可能需要验证餐厅是否有特定的食材,并将不需要做深盘披萨的食材放在一边。

当用代码写这个的时候,技术上你可以这样做

public class Pizza
{
    public void Prepare(PizzaType tp)
    {
        switch (tp)
        {
            case PizzaType.StuffedCrust:
                // prepare stuffed crust ingredients in system
                break;
                
            case PizzaType.DeepDish:
                // prepare deep dish ingredients in system
                break;
                
            //.... etc.
        }
    }
}

然而,深盘披萨(在c#术语中)可能需要在Prepare()方法中设置与填充披萨皮不同的属性,因此最终会有许多可选属性,并且类不能很好地伸缩(如果添加新的披萨类型会怎样)。

解决这个问题的正确方法是使用接口。界面声明所有披萨都可以准备,但每个披萨可以准备不同。如果你有以下接口:

public interface IPizza
{
    void Prepare();
}

public class StuffedCrustPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for stuffed crust preparations
    }
}

public class DeepDishPizza : IPizza
{
    public void Prepare()
    {
        // Set settings in system for deep dish preparations
    }
}

现在,您的订单处理代码不需要确切地知道订购了什么类型的披萨来处理配料。它只有:

public PreparePizzas(IList<IPizza> pizzas)
{
    foreach (IPizza pizza in pizzas)
        pizza.Prepare();
}

尽管每种类型的披萨都是不同的,但这部分代码并不需要关心我们处理的是哪种类型的披萨,它只知道它正在为披萨调用,因此每次调用Prepare都会根据其类型自动正确地准备每个披萨,即使集合有多种类型的披萨。

我在这个页面上搜索了“合成”这个词,但一次也没看到。这个答案是对前面提到的答案的补充。

在面向对象项目中使用接口的一个绝对重要的原因是,它们允许你更倾向于组合而不是继承。通过实现接口,您可以将您的实现与应用于它们的各种算法解耦。

德里克·巴纳斯(Derek Banas)的“装饰图案”教程(有趣的是,它也以披萨为例)是一个有价值的插图:

https://www.youtube.com/watch?v=j40kRwSm4VE