接口 - 有什么意义?

时间:2011-07-23 18:56:54

标签: c# .net interface

接口的原因真正让我失望。根据我的理解,它是一种解决C#中不存在的不存在的多重继承的方法(或者我被告知)。

我只看到,你预定义了一些成员和函数,然后必须再次在类中重新定义。从而使界面变得多余。它只是感觉像语法...好吧,垃圾对我来说(请不要冒犯意思。垃圾就像无用的东西)。

在下面给出的示例中,从堆栈溢出的不同C#接口线程中获取,我只是创建一个名为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
    }
}

34 个答案:

答案 0 :(得分:415)

没有人真正用简单的术语解释接口是如何有用的,所以我要试一试(并从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();
}

即使每种类型的披萨都有不同的准备,这部分代码也不必关心我们正在处理什么类型的披萨,它只是知道它被称为比萨饼,因此每次调用{{1即使该系列有多种类型的披萨,它也会根据其类型自动准备好每个披萨。

答案 1 :(得分:168)

关键是接口代表合同。任何实现类必须具有的一组公共方法。从技术上讲,接口只管理语法,即有哪些方法,它们得到什么参数以及返回什么。通常它们也会封装语义,尽管只能通过文档。

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

Python不是静态类型的,因此在运行时保留并查找类型。因此,您可以尝试在任何对象上调用Order()方法。只要对象有这样的方法并且可能只是耸耸肩并且说“Meh。”如果没有,那么运行时很开心。在C#中不是这样。编译器负责进行正确的调用,如果它只有一些随机object,编译器还不知道运行时的实例是否具有该方法。从编译器的角度来看,它无效,因为它无法验证它。 (您可以使用反射或dynamic关键字来执行此类操作,但我认为现在这样做有点远。)

另请注意,通常意义上的接口不一定必须是C#interface,它也可以是抽象类,甚至是普通类(如果所有子类都需要,它可以派上用场分享一些共同的代码 - 但在大多数情况下,interface就足够了。

答案 2 :(得分:103)

对于我来说,只有当你停止将它们视为使你的代码更容易/更快写入的东西时,这一点才变得清晰 - 这不是它们的目的。它们有很多用途:

(这将失去比萨饼的比喻,因为它不太容易想象出这个用途)

假设您正在屏幕上制作一个简单的游戏,它将拥有与您互动的生物。

答:通过在前端和后端实施之间引入松散耦合,他们可以使代码更易于维护。

你可以写这个开始,因为只有巨魔:

// 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);
}

两周后,市场决定你也需要兽人,因为他们在推特上阅读它们,所以你必须做类似的事情:

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;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    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,用于&#39;遗产&#39;原因没有很好的结构,A有B的参考。

然后,您在B中找到需要调用A中已有方法的功能。当您获得循环引用时,无法使用具体实现来执行此操作。

您可以在B中声明一个接口,然后A中的类实现。您在B中的方法可以传递一个实现接口的类的实例,没有问题,即使具体对象是A中的类型。

答案 3 :(得分:32)

以下是您重复解释的示例:

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
    }
}

答案 4 :(得分:20)

上面的例子没有多大意义。您可以使用类完成上述所有示例(如果您希望它仅作为契约行为,则为抽象类):

public abstract class Food {
    public abstract void Prepare();
}

public class Pizza : Food  {
    public override void Prepare() { /* Prepare pizza */ }
}

public class Burger : Food  {
    public override void Prepare() { /* Prepare Burger */ }
}

您获得与界面相同的行为。你可以创建一个List<Food>并迭代,不知道哪个类位于顶层。

更多适当的例子是多重继承:

public abstract class MenuItem {
    public string Name { get; set; }
    public abstract void BringToTable();
}

// Notice Soda only inherits from MenuItem
public class Soda : MenuItem {
    public override void BringToTable() { /* Bring soda to table */ }
}


// All food needs to be cooked (real food) so we add this
// feature to all food menu items
public interface IFood {
    void Cook();
}

public class Pizza : MenuItem, IFood {
    public override void BringToTable() { /* Bring pizza to table */ }
    public void Cook() { /* Cook Pizza */ }
}

public class Burger : MenuItem, IFood {
    public override void BringToTable() { /* Bring burger to table */ }
    public void Cook() { /* Cook Burger */ }
}

然后,您可以将所有这些用作MenuItem并且不关心他们如何处理每个方法调用。

public class Waiter {
    public void TakeOrder(IEnumerable<MenuItem> order) 
    {
        // Cook first
        // (all except soda because soda is not IFood)
        foreach (var food in order.OfType<IFood>())
            food.Cook();

        // Bring them all to the table
        // (everything, including soda, pizza and burger because they're all menu items)
        foreach (var menuItem in order)
            menuItem.BringToTable();
    }
}

答案 5 :(得分:19)

类比的简单解释

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

类比:所以我是建筑工地的前辈。

商人一直走在施工现场。我不知道谁会走过那些门。但我基本上告诉他们该怎么做。

  1. 如果是木匠,我会说:建造木制脚手架。
  2. 如果是管道工,我说:“设置管道”
  3. 如果是电工,我说,“拉出电缆,用光纤替换它们。”
  4. 上述方法的问题在于我必须:(i)知道谁在那扇门走路,并且根据它是谁,我必须告诉他们该做什么。这意味着我必须了解特定交易的一切。这种方法有相关的成本/收益:

    知道该怎么做的含义:

    • 这意味着如果木匠的代码从:BuildScaffolding()变为BuildScaffold()(即稍有名称更改),那么我还必须更改调用类(即{{1}同样 - 你必须对代码进行 两次 更改,而不是(基本上)只有一次。使用多态性,您(基本上)只需要进行一次更改即可获得相同的结果。

    • 其次,你不必经常问:你是谁?好的,这个......你是谁?确实这样做.....多态 - 它干掉那些代码,并且在某些情况下非常有效:

    • 使用多态性,您可以轻松添加其他类别的商人,而无需更改任何现有代码。 (即SOLID设计原则中的第二个:开放式原则)。

    解决方案

    想象一下这样一个场景,无论是谁走进门,我都可以说:“工作()”并且他们尊重他们专注的工作:水管工会处理管道,电工会处理电线

    这种方法的好处是:(i)我不需要知道究竟是谁通过那扇门进入 - 我需要知道的是,他们将成为一种传统,他们可以做的工作其次,(ii)我不需要知道有关该特定交易的任何信息。传统将负责这一点。

    所以不要这样:

    Foreperson

    我可以这样做:

    If(electrician) then  electrician.FixCablesAndElectricity() 
    
    if(plumber) then plumber.IncreaseWaterPressureAndFixLeaks() 
    

    有什么好处?

    好处是,如果木匠等的特定工作要求发生变化,那么老人不需要改变他的代码 - 他不需要知道或关心。重要的是木匠知道Work()的含义。其次,如果一个新型的建筑工人来到工地,那么工头不需要知道任何关于贸易的事情 - 所有工头的关心是建筑工人(例如焊工,格拉齐尔,泰勒等)能否得到一些Work()。

    图解的问题和解决方案(有和没有接口):

    无接口(示例1):

    Example 1: without an interface

    无接口(示例2):

    Example 2: without an interface

    使用界面:

    Example 3: The benefits of Using an Interface

    摘要

    界面允许您让人员完成他们所分配的工作,而无需了解他们究竟是谁或他们可以做什么的具体细节。这使您可以轻松添加新类型(交易)而无需更改现有代码(从技术上讲,您只需稍微更改一下),这就是OOP方法与更多功能编程方法的真正好处。

    如果您不理解上述任何内容,或者如果不清楚,请在评论中提出,我会尽力使答案更好。

答案 6 :(得分:11)

比萨的例子很糟糕,因为你应该使用一个处理排序的抽象类,比如披萨应该只是覆盖披萨类型。

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

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

答案 7 :(得分:11)

如果没有duck typing因为可以在Python中使用它,C#依赖于接口来提供抽象。如果类的依赖关系都是具体类型,则无法传入任何其他类型 - 使用可以在任何实现接口的类型中传递的接口。

答案 8 :(得分:10)

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

以视觉控件为例,在.NET for Winforms中,它们都继承自基类Control,这是在.NET框架中完全定义的。

我们假设您正在创建自定义控件。您想要构建新的按钮,文本框,列表视图,网格,诸如此类,并且您希望它们都具有您的控件集独有的某些功能。

例如,您可能需要一种常用的方法来处理主题,或者处理本地化的常用方法。

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

相反,您将从Button,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 ....
}

答案 9 :(得分:7)

在这种情况下,你可以(也可能)只定义一个Pizza基类并从中继承。但是,接口允许您执行其他方式无法实现的事情有两个原因:

  1. 一个类可以实现多个接口。它只定义了类必须具有的功能。实现一系列接口意味着一个类可以在不同的地方实现多个功能。

  2. 可以在类或者调用者之间的hogher范围中定义接口。这意味着您可以分离功能,分离项目依赖项,并将功能保留在一个项目或类中,以及在其他地方实现此功能。

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

答案 10 :(得分:6)

请考虑您不能在C#中使用多重继承,然后再次查看您的问题。

答案 11 :(得分:5)

Interface = contract,用于loose coupling(请参阅GRASP)。

答案 12 :(得分:4)

接口实际上是一个实现类必须遵循的契约,它实际上是我所知道的几乎所有设计模式的基础。

在您的示例中,创建了界面,因为任何是A Pizza,这意味着实现了Pizza接口,保证已实现

public void Order();

在您提到的代码之后,您可能会遇到以下情况:

public void orderMyPizza(IPizza myPizza) {
//This will always work, because everyone MUST implement order
      myPizza.order();
}

这样你就可以使用多态,所有你关心的是你的对象响应order()。

答案 13 :(得分:4)

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

所以你打电话给工厂方法:MyInterface i = MyGraphics.getInstance()。然后,你有一份合同,所以你知道在MyInterface中你可以期待什么功能。因此,您可以调用i.drawRectanglei.drawCube,并知道如果您将一个库换成另一个库,则支持这些函数。

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

因此,您可能有一个可以导出的加密库供一般使用,另一个只能出售给美国公司,不同之处在于您更改了配置文件,其余程序没有改变。

这在.NET中大量使用集合,因为你应该使用例如List个变量,不要担心它是ArrayList还是LinkedList。

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

这在单元测试时也很有用,因为你可以模拟整个接口,所以,我不必去数据库,而是去一个只返回静态数据的模拟实现,所以我可以测试我的方法无需担心数据库是否因维护而停机。

答案 14 :(得分:3)

我在此页面上搜索了“组合”这个词,但没有看到过一次。除了上述答案之外,这个答案非常有用。

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

Derek Banas的这个精美的“装饰模式”教程(有趣的是 - 也以披萨为例)是一个值得说明的例子:

http://webcomponents.org/articles/introduction-to-html-imports/

答案 15 :(得分:3)

什么?

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

C#中,按惯例,接口名称是通过在前缀I之前定义的,因此,如果要使用一个名为shapes的接口,则可以将其声明为IShapes

现在为什么?

Improves code re-usability

假设您要绘制CircleTriangle. 您可以将它们组合在一起并命名为Shapes,并具有绘制CircleTriangle的方法 但是具体实施将不是一个好主意,因为明天您可能会决定再拥有2个Shapes RectangleSquare。现在,当您添加它们时,很有可能会破坏代码的其他部分。

使用接口,您可以将不同的实现与合同隔离


现场情况第1天

系统要求您创建一个用于绘制圆形和三角形的应用程序

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();
        }
}

现场情景第2天

如果要求您在其中添加SquareRectangle,您要做的就是在class Square: IShapes中为其创建一个标准,并在Main中添加到列表{ {1}}

答案 16 :(得分:2)

我很惊讶没有多少帖子包含界面最重要的原因:设计模式。这是使用契约的更大图景,虽然它是机器代码的语法修饰(说实话,编译器可能只是忽略它们),但抽象和接口对于OOP,人类理解和复杂的系统架构至关重要。

让我们将披萨比喻扩展为完整的3道菜套餐。我们仍然会为我们所有的食物类别提供核心Prepare()界面,但我们也会对课程选择(开胃菜,主菜,甜点)和食物类型的不同属性(咸味/甜味,素食)进行抽象声明。 /非素食,无麸质等)。

基于这些规范,我们可以实现抽象工厂模式来概念化整个过程,但使用接口来确保只有基础是具体的。其他所有内容都可以变得灵活或鼓励多态,同时保持实现Course接口的ICourse的不同类之间的封装。

如果我有更多时间,我想制定一个完整的例子,或者有人可以为我扩展这个,但总的来说,C#接口将是设计这种类型系统的最佳工具。 / p>

答案 17 :(得分:2)

这是具有矩形形状的对象的接口:

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

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

现在让我们定义一个适用于IRectangular的任何对象的方法:

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));

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

答案 18 :(得分:2)

你将获得接口,当你需要它们时:)你可以学习例子,但你需要Aha!真正得到它们的效果。

现在你知道接口是什么,只是没有它们的代码。迟早你会遇到一个问题,接口的使用将是最自然的事情。

答案 19 :(得分:2)

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

public class Car { ... }

public class Tree { ... }

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

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

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

答案 20 :(得分:2)

接口定义特定功能的提供者与对应消费者之间的契约。它将实现与合同(接口)分离。您应该看一下面向对象的体系结构和设计。您可能想要从维基百科开始:http://en.wikipedia.org/wiki/Interface_(computing)

答案 21 :(得分:1)

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

您可能熟悉面向对象设计的SOLID原则。总结:

S - 单一责任原则 O - 开放/封闭原则 L - Liskov替换原则 I - 接口隔离原理 D - 依赖倒置原则

遵循SOLID原则有助于生成干净,考虑周全,内聚和松散耦合的代码。鉴于:

  

&#34;依赖管理是各种规模软件中的关键挑战。 (唐纳德克努特)

然后,任何有助于依赖管理的事情都是一个巨大的胜利。接口和依赖性倒置原则确实有助于将代码与具体类的依赖性分离,因此可以根据行为而不是实现来编写和推理代码。这有助于将代码分解为可以在运行时而不是编译时组成的组件,并且还意味着可以非常容易地插入和移出这些组件,而无需更改其余代码。

接口特别有助于依赖性倒置原则,其中代码可以组件化为一组服务,每个服务由接口描述。然后可以注入服务&#34;通过将它们作为构造函数参数传递给运行时的类。如果您开始编写单元测试并使用测试驱动开发,这种技术真的变得至关重要。试试吧!您将很快了解接口如何帮助将代码拆分为可管理的块,这些块可以单独进行单独测试。

答案 22 :(得分:1)

接口以松散耦合的方式用于驱动一致性,这使其与紧密耦合的抽象类有所不同。这就是为什么它也通常定义为协定的原因。实现该接口的所有类均遵守“规则” / syntax”由接口定义,并且其中没有具体元素。

我只给出下图支持的示例。

想象一下,在工厂中有3种类型的机器。矩形机器,三角形机器和多边形机器。时间竞争激烈,您想简化操作员培训。您只想用一种启动和停止方法进行培训因此,现在在3台不同的机器上,您可以使用一致的方式启动和停止3种不同类型的机器。现在想象这些机器是类,并且这些类需要具有start和stop方法,您如何在这些可能非常不同的类之间提高一致性?界面就是答案。

enter image description here

一个简单的示例可以帮助您可视化,可能会问为什么不使用抽象类?使用接口,对象不必直接相关或继承,您仍然可以在不同类之间实现一致性。

(?=Remediation)[^\n]*[\s\S]*?(?=References|$)

答案 23 :(得分:1)

我知道我已经很晚了(将近九年),但是如果有人想要小的解释,那么您可以这样做:

简单来说,当您知道对象可以做什么或我们将要在对象上实现的功能时,便可以使用接口。示例插入,更新和删除。

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

重要说明:接口始终是公共的。

希望这会有所帮助。

答案 24 :(得分:0)

接口也可以菊花链式连接以创建另一个接口。这种实现多个接口的能力为开发人员提供了向其类添加功能的优势,而无需更改当前的类功能(SOLID Principles)

O =&#34;类应该是为扩展而打开但是关闭以进行修改&#34;

答案 25 :(得分:0)

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

  1. CC类可以使用C类的任何公共成员或受保护成员,就好像它们是自己的成员一样,因此只需要实现父类中不存在的内容。
  2. 对CC的引用可以传递或分配给期望引用C的例程或变量。

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

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

这个的一个很好的特性(在.net框架中未充分利用,恕我直言)是它们可以以声明的方式指示对象可以做的事情。例如,某些对象需要数据源对象,他们可以通过索引检索事物(可以使用List),但是它们不需要存储任何内容。其他例程需要一个数据存储对象,它们可以不通过索引存储事物(与Collection.Add一样),但它们不需要读回任何东西。某些数据类型允许通过索引访问,但不允许写入;其他人将允许写作,但不允许通过索引访问。当然,有些人会允许两者。

如果ReadableByIndex和Appendable是不相关的基类,那么就不可能定义一个类型,它可以传递给期望ReadableByIndex和期望Appendable的东西。人们可以尝试通过让ReadableByIndex或Appendable派生自另一个来缓解这种情况;派生类必须为这两个目的提供公共成员,但警告某些公共成员可能实际上不起作用。微软的一些类和接口可以做到这一点,但那很蠢。更简洁的方法是为不同的目的提供接口,然后让对象为他们实际可以做的事情实现接口。如果有一个接口IReadableByIndex和另一个接口IAppendable,可以做其中一个的类可以为他们可以做的事情实现适当的接口。

答案 26 :(得分:0)

Therese会问很好的例子。

另一种情况是,在switch语句的情况下,每次希望rio以特定方式执行任务时,您都不再需要维护和切换。

在您的披萨示例中,如果您想制作披萨,那么界面就是您所需要的,从那里每个披萨都会照顾它自己的逻辑。

这有助于减少耦合和圈复杂度。你必须仍然实现逻辑,但是你必须在更广泛的图片中跟踪。

对于每个披萨,您可以跟踪特定于该披萨的信息。其他比萨饼有什么不重要,因为只有其他比萨饼需要知道。

答案 27 :(得分:0)

接口的主要目的是在您和实现该接口的任何其他类之间签订合同,使您的代码解耦并允许扩展。

答案 28 :(得分:0)

对我来说,接口的优点/好处是它比抽象类更灵活。由于您只能继承1个抽象类,但可以实现多个接口,因此对在许多地方继承一个抽象类的系统进行的更改将成为问题。如果在100个地方继承,则更改需要将所有100个更改。但是,使用该接口,您可以将新更改放置在新接口中,并仅在需要的地方使用该接口(来自SOLID的接口序列)。此外,接口的内存使用情况似乎更少,因为尽管有多少地方实现了接口,接口示例中的对象在内存中仅使用一次。

答案 29 :(得分:0)

让我们举个例子。假设我是一个机器人,并且可以向周围的人开枪。但是,问题是我没有枪。我也不明白枪支是如何工作的。但我知道,枪支是一种带有扳机的物体,按下该扳机会杀死人。因此,此代码如下所示:-

public class Robot{

    public void ShootPeopleWith( weapon IWeapon ){
        weapon.PressTrigger()
    }
}

interface IWeapon{
   PressTrigger()
}

您看到机器人希望任何想要它射击的人都首先向他提供武器。现在,他不知道该使用哪种武器。无论您提供给我什么,它都应该有一个触发器,因为我只知道按下触发器,并且知道它可以达到我的目的。如果您为我提供没有触发器的东西,我将无法运行。

现在,有架无人机指示机器人射击人。它还为它提供了枪支。

public class Drone{
    List<Robots> allRobotsInArea = someList

    public void DelegateARobot(){
        robot = select a robot from allRobotsInArea
        IWeapon weapon = new MachineGun();    //procuring a machine gun
        robot.ShootPeopleWith(weapon);
    }

}

您可以在此处看到IWeapon是机器人与无人机之间的协议。它说,无论您给我什么,都必须有一个触发器。因此,机枪必须具有扳机。让我们实现一些武器:-

public class MachineGun : IWeapon{
    public void PressTrigger(){
        Fire40RoundsPerSecond();
    }
    ...
}

public class Sniper() : IWeapon{
    public void PressTrigger(){
        SayQuackQauck();
    }
}

无人机现在可以轻松将狙击手和MachineGun之类的任何武器传递给机器人。

public class Pumpkin{
    public void FreakPeopleOut(){
        GlowInTheDark();
    }
}

无人驾驶飞机即使可以用作robot.ShootPeopleWith(new Pumpkin());的武器也无法通过南瓜,因为它没有扳机,并且违反了机器人期望的合同。 您现在可能会争辩说,为什么机器人不能期望父类武器而不是接口IWeapon?因为编写Robot类的人不想定义父类。理想情况下,使用Robot的人应该根据Robot希望使用Robot达成的合同来定义父类(如果尚不存在)。

答案 30 :(得分:0)

class Program {
    static void Main(string[] args) {
        IMachine machine = new Machine();
        machine.Run();
        Console.ReadKey();
    }

}

class Machine : IMachine {
    private void Run() {
        Console.WriteLine("Running...");
    }
    void IMachine.Run() => Run();
}

interface IMachine
{
    void Run();
}

让我用不同的角度来描述这一点。让我们根据上面显示的示例创建一个故事;

程序,机器和IMachine是我们故事的参与者。程序想运行,但是它不具备这种能力,并且机器知道如何运行。 Machine和IMachine是最好的朋友,但是Program与Machine并不是一成不变的。因此,Program和IMachine达成协议,并决定IMachine通过看机器(像反射器)来告诉Program如何运行。

程序会在IMachine的帮助下学习如何运行。

接口可提供通信并开发松散耦合的项目。

PS::我将具体课程设为私有课程。我的目的是通过防止访问具体的类属性和方法来实现松散耦合,而只允许通过接口访问它们。 (因此,我明确定义了接口的方法)。

答案 31 :(得分:0)

答案太多了! 尽我所能。呵呵。

所以首先,是的,您可以在这里使用具体的基类和派生类。在这种情况下,您将不得不为基类中的 Prepare 方法做一个空的或无用的实现,也使此方法 virtual,然后派生类将覆盖此 Prepare 方法。这种情况下,Base类中Prepare的实现就没有用了。

您选择使用 Interface 的原因是因为您必须定义合同,而不是实现

有一个 IPizza 类型,它提供了准备的功能。这是契约。它的准备方式是实施,而不是您的监视。它负责各种 Pizza 实现。 interfaceabstract 类在这里比具体基类更受欢迎,因为您必须创建一个抽象,即 Prepare 方法。您不能在具体基类中创建抽象方法。

现在你可以说,为什么不使用抽象类?

因此,当您需要实现 100% 抽象时,您需要使用 Interface。但是,当您需要一些抽象和具体实现时,请使用 abstract 类。意思是。

示例:假设您的所有比萨饼都有底料,底料的制备过程相同。但是,所有比萨饼类型和配料都会有所不同。在这种情况下,您可以使用抽象方法 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.
    }
}

答案 32 :(得分:-1)

我是否正确然后再看看Interfaces,图形界面(winforms / WPF)就像界面。它仅显示最终用户将与之交互的内容。最终用户将不必知道应用程序的设计内容,而是根据表单上的可用选项知道他们可以使用它做什么。在OOP视图中,我们的想法是创建一个结构化的接口,通知您的例如DLL库的其他用户可以使用的内容,并且它类似于保证/合同,字段,方法和属性可供使用(继承在您的类)。

答案 33 :(得分:-1)

我同意你认为接口不是必需的。以下是来自Cwalina第80页框架设计指南的引用“我经常在这里人们说接口指定合同。我认为这是一个危险的神话。接口本身并没有明确说明......”他和合着者Abrams管理了3个版本of .Net for Microsoft。他接着说,“合同”是在课堂实施中“表达”的。恕我直言看了几十年,有很多人警告微软在OLE / COM中采用工程范例可能看起来不错,但它的用处更直接于硬件。特别是在80年代和90年代,大规模的互操作标准被编成法典。在我们的TCP / IP互联网世界中,我们很难理解硬件和软件体操,以便在大型机,小型机和微处理器之间获得解决方案“连线”,其中PC只是少数几个。因此,编码到接口及其协议使计算工作。界面统治。但解决使X.25与您的应用程序一起工作的问题与发布假期的食谱有什么共同之处?我已经编写C ++和C#多年了,我从来没有创建过一次。