【问题标题】:Significance of Interfaces C#接口 C# 的意义
【发布时间】:2010-02-08 10:47:51
【问题描述】:

我想知道接口的重要用途。我读了很多文章,但对接口的概念并不清楚。

我写了一个小程序。我已经定义了接口Itest.Class(Manager)已经实现了Interface.Another class(Employee)还没有实现接口。但是在类(Employee)的接口中定义了相同的方法(DoSomething())。我可以从类对象中调用该方法。那我为什么要去实现接口。我可以直接在一个类中实现该方法并调用该方法。为什么我要在接口中实现方法的额外步骤,然后按类继承接口。我知道接口支持多重继承,但在这个例子中我没有使用多重继承。

感谢您的任何想法或意见。

public interface Itest
{
    void DoSomething();
}

public class Manager:Itest
{
    public void DoSomething()
    {
        Console.WriteLine("test....");
    }

}
class Employee
{
    public void DoSomething()
    {
        Console.WriteLine("test....");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Manager m = new Manager();
        m.DoSomething();
        Employee e = new Employee();
        e.DoSomething();
        Console.ReadLine();
    }
}

【问题讨论】:

  • 为什么不选择一个有多重继承的例子,例如经理支持ITestISerializableICloneable 等。那么你无疑会回答你自己的问题!
  • 所以接口基本上用来实现多个继承。没有其他用途。
  • 这几乎是 stackoverflow.com/questions/2151959/using-interface-variables 的重复——那里的答案可能会回答您的问题(另请参阅已接受答案的 cmets)。
  • Shikha: "接口主要用于实现多重继承。没有其他用途。"错误的。接口用于实现多态性。它们绝对有多重继承以外的用途。例如,为确定性发布语义和using 支持实现IDisposable,为foreach 和LINQ 支持实现IEnumerable,等等。
  • @itowlson - 但是类也这样做。具有所有抽象方法的抽象类在逻辑上与接口相同,除了一件事:单个类可以继承多个接口。 MI 是接口的唯一独特价值。

标签: c# interface


【解决方案1】:

接口允许您使用多重继承。在您的示例中,它将允许您将 Employee 或 Manager 的实例放入同一个变量中,然后对该变量调用 DoSomething,并将方法调用分派到该变量当前引用的实例。例如:

public interface IEmployee {
    void DoSomething();
}
// assume Manager and Employee both implement IEmployee

IEmployee ie = new Manager();
ie.DoSomething();    // calls Manager.DoSomething()
ie = new Employee();
ie.DoSomething();    // calls Employee.DoSomething()

如果你不使用接口,你必须这样做:

object o;

// assign Manager or Employee (or something else!) to o

if (o is Manager)
    ((Manager)o).DoSomething();
else if (o is Employee)
    ((Employee)o).DoSomething();

一个接口定义了一个契约,只要一个实例实现了那个接口,你就不必关心它在运行时的实际情况。您可以让同一个类实现多个接口,然后在这些接口的所有变量中使用该类的实例。您不能对抽象类使用相同的方法,因为一个类一次只能继承一个类。

我现在使用接口的一个例子是定义一个对象模型——我有各种属性的接口(@98​​7654324@、IHasPrivilegesIHasCheezburger),然后代表具体对象的类实现任何一个然而,许多接口都适合该类的属性

【讨论】:

  • +1 因为对 MI 的支持是接口的 唯一 值(与抽象类/方法相比)。
  • 而且,为了放大,您的第二个代码片段 still 无法完成这项工作,因为它无法处理无法处理的事情-that-c​​ould-do-DoSomething调用例程的作者所预期的。如果我稍后引入一个 Contractor 类,基于接口的方法将继续运行,但必须更新 if (o is X) 例程以处理新情况。
  • 实例化类的所有者应将其存储为最派生的类型,即“Manager m = new Manager();”。否则,您将删除所有其他可能很重要的属性。例如如果 Manager 实现 IDisposable。非拥有方法应使用所需的最少派生类型。
  • @adrianm:嗯?您通常应该尽可能使用非特定类型。通过不太特定的类型引用特定类型的实例并不会“擦除”其他属性,它只是限制了调用代码可以对对象执行的操作——这就是重点。
  • @adrianm: 这只是一个使用接口调用方法的简单例子:)
【解决方案2】:

接口用于抽象(抽象类也用于此目的,但通常包含一些用于重用的实现)。

在 C# 中,它们允许使用多重继承,这意味着您可以在一个类中实现许多不同的接口。

如果您有许多不同的接口实现,则可以将它们相互替换,只要您使用接口声明即可。

例如:

IAnimal 可以由CatDog 实现。在其他代码中,您要调用接口中声明的Talk 方法。您的代码不必关心它是Cat 对象还是Dog 对象。您可以添加 DuckHuman 而不更改那段代码。

这在使用模拟对象测试您的代码时也很有用,因此可以用一个简单的对象代替真实的对象来进行测试。

一些接口被用作标记,因此反射可以很容易地提取它们(例如ISerializable 接口,将类标记为可序列化)。

【讨论】:

  • “接口用于抽象” - 抽象类/方法也是如此,因此得名。
  • 接口是OO编程中存在的最抽象的概念。抽象类比接口更具体,通常实现一些接口。重要的是概念,而不是特定语言如何称呼概念。
【解决方案3】:

实现继承对“IS A KIND OF”关系建模,而接口继承对“CAN BEHAVE LIKE”关系建模。许多 BCL 接口名称都以“-able”结尾,这并非巧合,代表了做某事的能力。为了演示,图像如下代码:

class Person
{
  public string Name{get;set;}
  public void Walk() {/*code elided*/}
}

class Employee : Person
{
  public int Id{get;private set;}
}

显然,Employee“是一种”Person。所有员工都是人,因此他们都有一个 Name 并且可以 W​​alk()。无论如何,将 Person 或 Employee 抽象化,这并不会改变所有员工都是人的事实。

现在让我们稍微抽象一点,谈谈作为车辆的概念。一个东西成为交通工具的绝对必要条件是它必须能够移动和停止。您可能包括转向和载客,但我保持非常抽象。

让我们考虑一些车辆。一辆汽车,当然,但一个人呢?他们可以移动和停止(当我背着我的侄子和侄女时,我也在载乘客!)轮椅?我总是在办公室里发呆。我也是一个热心的水手,利用风来加速和减速我的车辆。

您不能使用实现继承来模拟这种关系,因为您有很多不同的东西“可以像”车辆一样,但不一定从同一个基类继承。

pogostick(向专业 pogo-ers 道歉)是一种玩具,但可以充当车辆。并非所有玩具都是交通工具。人与汽车没有任何关系,除了它可以充当车辆这一事实。

interface IVehicle
{
  void Move();
  void Stop();
}

class Toy{}

class PogoStick : Toy, IVehicle
{
  public void Move(){ /* boing boing */}
  public void Stop(){ /* fall over */}
}

class Car: IVehicle
{
  public void Move(){ /* vroom vroom */}
  public void Stop(){ /* <screeeech!> */}
}

class Person : IVehicle
{
  public string Name{get;set;}
  public void Walk() {/*code elided*/}
  void IVehicle.Move() { Walk(); }
  void IVehicle.Stop() { /*whatever!*/}
}

class Program
{
  static void Main()
  {
    IVehicle[] vehicles = new IVehicle[3];
    vehicles[0] = new PogoStick();
    vehicles[1] = new Car();
    vehicles[2] = new Employee(); //implements IVehicle because it IS A KIND OF Person

    vehicles.ForEach(v => v.Move());

    //it's worth pointing out that
    vehicles[2].Stop();
    //works fine, but
    Person p = new Person();
    p.Move();
    //won't, as I explicitly implemented the interface, meaning I can only get at the
    //methods via a reference to the interface, not to the implementing class.
  }
}

举个 .NET 本身的例子,字符串和 List 到底有什么共同点?不是很多,除了我可以“foreach”他们两个:

class Demo
{
  static void Main()
  {
    string s = "Hello!";
    List<Employee> payroll = new List<Employee>();

    for each (var item in s)
    {
      Console.WriteLine(item);
    }

    for each (var item in payroll)
    {
      Console.WriteLine(item);
    }
}

string 和 List 的通用基类是 object,但并非所有对象都是“for-each-able”的,所以必须有其他事情发生。即它们都实现了 IEnumerable 接口(有那个-able!)

【讨论】:

    【解决方案4】:

    当您将接口添加到 Employee 时,两个类通过该接口变得兼容:

    public class Manager:Itest { ... }
    
    class Employee:Itest { ... }
    
    static void ShowSomething(Itest test)
    {
       test.DoSomething();
    }
    
    static void Main(string[] args)
    {
    
        Manager m = new Manager();            
        Employee e = new Employee();
    
        ShowSomething(m);
        ShowSomething(e);
    }
    

    看看BCL如何使用接口,开始查找IDisposable、ISerializable、IComprable和IEnumerable

    【讨论】:

      【解决方案5】:

      接口的继承使您可以在不破坏代码的情况下使用其他类或对象更改您的实现。接口意味着您与代码的客户签订合同,您将提供一些功能,他们不必知道为此调用哪个特定类。 接口添加了一个抽象层,因此您的代码客户端将不依赖于您如何实现解决方案。他们只知道你会向他们提供界面中的内容。

      【讨论】:

      • 抽象类也是如此——除了接口它更糟糕。如果将方法添加到抽象类,则可以提供默认实现。如果你尝试对一个接口做同样的事情,你会破坏所有实现它的库。
      • 抽象类有不同的用途。对层次结构常见的一些行为进行编码并避免代码重复。
      【解决方案6】:

      接口在更复杂的场景中很有用,例如

      (1) 您需要多重继承(在 c# 中您不能从 2 个类继承),例如你有接口 IProduct,IDisposable。并非所有产品都需要处置,因此在所有产品等上实施它是没有意义的。

      (2) 当您使用依赖注入(控制反转)和模拟框架(例如 RhinoMocks)进行单元测试时,您必须使用接口,否则您的模拟框架将无法工作。

      【讨论】:

      • 如果我不使用MI,界面没有用?
      【解决方案7】:

      你知道,很有趣,我今天刚刚和我们的一位开发人员谈论这个话题。

      对我来说,解释什么是界面及其可用性的最简单方法是以下示例。

      interface IGraphicsRenderer
      {
           Render(List<Triangle> triangles);
      }
      

      那么你可能有 2 种渲染引擎,Direct3D 或 OpenGL

      class Direct3DRenderer : IGraphicsRenderer
      {
         public void Render(List<Triangle> triangles);
      }
      
      class OpenGLRenderer: IGraphicsRenderer
      {
         public void Render(List<Triangle> triangles);
      }
      

      为了显示它的用处,你可能有类似的东西

      IGraphicsRenderer renderer = new Direct3DRenderer();
      renderer.Render(foo);
      

      要更改渲染器,您需要做的就是更改初始化。

      IGraphicsRenderer renderer = new OpenGLRenderer();
      

      【讨论】:

        【解决方案8】:

        提供了一个很好的接口类比Matthew Cochran

        “这使导航变得更容易”代码世界“。想象一下,如果不是学习如何驾驶汽车然后能够驾驶任何汽车,我们必须学习如何驾驶每辆车的每个实例我们进入了。如果在学习了如何驾驶福特平托之后,我们必须重新开始以找出野马,那将是非常低效的。更有效的方法是处理汽车界面:方向盘,转向灯、油门和刹车。这样一来,无论界面后端实现了什么,我们都不在乎,因为最终它订阅了基本的汽车合约,这就是我们将如何处理它(通过界面)。2

        除了上面的一般解释外,大多数现代软件设计模式都非常依赖于依赖注入等接口

        考虑这个例子:

        您有一个能够播放媒体文件(mp3)的类。你把这门课给你尝试播放 MPEG 类型文件的朋友。如果不对你的班级做出重大改变,他就不可能这样做。

        public class MusicPlayer 
            {
               void Play(Mp3 _mp3File){}
            }
        

        考虑一下

        如果你传递这个方法,而不是传递 mp3 文件的类型到播放方法怎么办,一个派生自类型 MediaType 的接口。

            public interface MediaType { }
        
            public class Mp3 : MediaType
            { }
        
            public class MPEG : MediaType
            { }
        

        和班级:

            public class MusicPlayer 
            {
               void Play(MediaType _mediaFile){}
            }
        

        在这种情况下,您可以从 MPEG 等 MediaType 派生另一个 MediaFile 类型并将其传递给 Play 方法,它会很高兴地接受它并为您播放(提供的逻辑)。

           public class TestPlayers
            {
                public void PlayMedia()
                {
                    MusicPlayer musicPlayer = new MusicPlayer();
                    musicPlayer.Play(new Mp3());
                    musicPlayer.Play(new MPEG());
                }       
            }
        

        希望对你有帮助

        【讨论】:

        • 以上所有对于抽象类同样适用。
        • Earwicker:你确定我们可以用抽象类做上面的事情吗?你有任何例子或链接......
        【解决方案9】:

        当你有太多从接口类驱动的类时,你要确保它们都实现了一个方法。

        如果你的界面发生变化(例如:

               public Interface IAnimal
                    {
                        public void Talk();
                        public void Eat();
                    }
        

        然后你添加另一个方法

               public Interface IAnimal
                    {
                        public void Talk();
                        public void Sleep();
                        public void Eat();
                    }
        

        然后您可以确保它们都将实现 Sleep() 方法。如果您有太多来自 IAnimal 接口的类,那么您必须为所有这些类实现 Sleep()。这可以帮助您尽可能轻松地扩展派生类

        【讨论】:

          【解决方案10】:

          当然你可以不实现接口就实现接口中声明的方法。简单地说,接口只是确保你不会忘记它(如果任何接口成员没有实现,它们会给你编译错误)。

          【讨论】:

          • 抽象基类中的抽象方法也是如此。
          • 是的,这是真的。不同的是,当抽象类可以有代码做某事时,接口只给出方法的描述。
          • 抽象类中的抽象方法(见我的第一条评论)只给出了方法的描述,就像接口中的方法一样。接口独有的唯一功能是多重继承。
          【解决方案11】:

          我假设你想要一个非常简单的答案。

          继承有两种类型:

          • TYPE 1:接口继承(在 简单来说:当在外面 类被继承)
          • 类型 2: 实现继承(当 类内部被继承)

          如果你写

          class MyClass : Base {}
          

          您同时使用类型 1 和 2。但如果您实现的接口是明确的类型 1。

          类型 1 用于多态使用,类型 2 用于代码重用。

          所以底线是如果你想使用多态但你

          • 不想提供任何 实施
          • 或者根本做不到。主要是在多重继承的情况下 - 注意:c++ 允许它,它遵循另一种哲学

          界面适合你:)

          还有其他用途(例如强制执行方法),但我认为这就是重点。

          【讨论】:

            【解决方案12】:

            接口用于设计。实际实现遵循设计中定义的内容(读取接口)。接口的概念为设计系统提供了灵活性,而无需深入了解实现的细节,或者通过在未来实现接口来保持设计的可扩展性。这也称为抽象。

            虽然接口在设计时提供了灵活性,但它在实现时施加了约束。接口的任何实现都需要是完整的,这意味着需要在实现时实现接口中定义的所有规范。此时的另一个灵活性是可以在一个类中实现多个接口。

            这是一个非常强大的概念,在设计时提供了非常高的灵活性,并确保实施不会破坏设计。

            【讨论】:

              猜你喜欢
              • 2010-10-23
              • 2016-03-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-10-07
              • 1970-01-01
              • 2010-10-09
              相关资源
              最近更新 更多