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

当前回答

考虑接口的最简单方法是认识继承的意义。如果类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.

其他回答

没有人真正清楚地解释过接口是如何有用的,所以我打算尝试一下(并从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都会根据其类型自动正确地准备每个披萨,即使集合有多种类型的披萨。

这里有很多很好的答案,但我想从一个稍微不同的角度来尝试。

你可能熟悉面向对象设计的SOLID原则。总而言之:

S -单一责任原则 O -开/闭原则 利斯科夫替换原理 I -界面隔离原理 D -依赖倒置原理

遵循SOLID原则有助于生成干净、分解良好、内聚和松散耦合的代码。考虑到:

用法与例句:“依赖管理是软件在任何规模上的主要挑战”(唐纳德·克努特)

那么任何有助于依赖管理的东西都是一个巨大的胜利。接口和依赖倒置原则确实有助于将代码与具体类的依赖解耦,因此可以根据行为而不是实现来编写和推理代码。这有助于将代码分解成可以在运行时而不是编译时组合的组件,也意味着这些组件可以很容易地插入和取出,而无需更改其余代码。

Interfaces help in particular with the Dependency Inversion Principle, where code can be componentized into a collection of services, with each service being described by an interface. Services can then be "injected" into classes at runtime by passing them in as a constructor parameter. This technique really becomes critical if you start to write unit tests and use test driven development. Try it! You will quickly understand how interfaces help to break apart the code into manageable chunks that can be individually tested in isolation.

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

以可视化控件为例,在。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 ....
}

类比简单解释

无接口(例1):

无接口(例2):

有接口:

需要解决的问题:多态性的目的是什么?

比方说,我是一个建筑工地的领班。我不知道哪个商人会走进来。但我告诉他们该怎么做。

如果是木匠,我会说:搭建木质脚手架。 如果是管道工,我会说:安装管道 如果是人民党政府官员,我说,三袋现金,先生。

上述方法的问题在于:(1)我必须知道谁会走进那扇门,并且根据是谁,我必须告诉他们该做什么。这通常会使代码更难维护或更容易出错。

知道该做什么的含义:

This means if the carpenter's code changes from: BuildScaffolding() to BuildScaffold() (i.e. a slight name change) then I will have to also change the calling class (i.e. the Foreperson class) as well - you'll have to make two changes to the code instead of (basically) just one. With polymorphism you (basically) only need to make one change to achieve the same result. Secondly you won't have to constantly ask: who are you? ok do this...who are you? ok do that.....polymorphism - it DRYs that code, and is very effective in certain situations: with polymorphism you can easily add additional classes of tradespeople without changing any existing code. (i.e. the second of the SOLID design principles: Open-close principle).

解决方案

想象一下这样一个场景:无论谁走进来,我都可以说:“Work()”,他们做着自己擅长的工作:管道工处理管道,电工处理电线,官僚可能专门负责受贿,为其他人做双倍的工作。

这种方法的好处是:(i)我不需要确切地知道谁会走进那扇门——我只需要知道他们是一种手工工人,他们会工作,其次,(ii)我不需要知道任何关于特定行业的信息。手工工人会处理的。

所以不要这样:

if(electrician) then  electrician.FixCablesAndElectricity() 

if(plumber) then plumber.IncreaseWaterPressureAndFixLeaks() 

if(keralaCustoms) then keralaCustoms.askForBribes() 

我可以这样做:

ITradesman tradie = Tradesman.Factory(); // in reality i know it's a plumber, but in the real world you won't know who's on the other side of the tradie assignment.

tradie.Work(); // and then tradie will do the work of a plumber, or electrician etc. depending on what type of tradesman he is. The foreman doesn't need to know anything, apart from telling the anonymous tradie to get to Work()!!

有什么好处?

这样做的好处是,如果木匠等特定的工作要求发生了变化,那么领班就不需要改变他的代码——他不需要知道或关心。重要的是木匠知道Work()是什么意思。其次,如果一种新型的建筑工人来到工地上,那么工头不需要知道任何关于贸易的事情——工头所关心的是建筑工人(.e。焊工、上釉工、瓦工等)可以完成一些工作。

总结

界面允许您让人完成分配给他们的工作,而不需要您确切地知道他们是谁或他们可以做什么。这允许您轻松地添加新的(交易)类型,而无需更改现有的代码(从技术上讲,您确实更改了一点点),这是面向对象方法相对于更函数式编程方法的真正好处。

如果你不理解上面的任何内容,或者不清楚,请在评论中提问,我会尽量让答案更好。

下面是一个矩形对象的接口:

interface IRectangular
{
    Int32 Width();
    Int32 Height();
}

它所要求的是实现访问对象宽度和高度的方法。

现在让我们定义一个方法,它可以作用于任何irectangle对象:

static class Utils
{
    public static Int32 Area(IRectangular rect)
    {
        return rect.Width() * rect.Height();
    }
}

这将返回任意矩形物体的面积。

让我们实现一个矩形的类SwimmingPool:

class SwimmingPool : IRectangular
{
    int width;
    int height;

    public SwimmingPool(int w, int h)
    { width = w; height = h; }

    public int Width() { return width; }
    public int Height() { return height; }
}

另一个类House也是矩形的:

class House : IRectangular
{
    int width;
    int height;

    public House(int w, int h)
    { width = w; height = h; }

    public int Width() { return width; }
    public int Height() { return height; }
}

鉴于此,你可以在房屋或游泳池上调用Area方法:

var house = new House(2, 3);

var pool = new SwimmingPool(3, 4);

Console.WriteLine(Utils.Area(house));
Console.WriteLine(Utils.Area(pool));

通过这种方式,您的类可以从任意数量的接口“继承”行为(静态方法)。