【问题标题】:Using Interface variables使用接口变量
【发布时间】:2010-01-28 02:51:21
【问题描述】:

我仍在努力更好地理解接口。我知道它们是什么以及如何在类中实现它们。

我不明白的是,当您创建一个属于您的接口类型之一的变量时:

IMyInterface somevariable;

你为什么要这样做?我不明白如何像类一样使用 IMyInterface...例如调用方法,所以:

somevariable.CallSomeMethod();

为什么要使用 IMyInterface 变量来执行此操作?

【问题讨论】:

标签: c#


【解决方案1】:

您不是在创建接口的实例 - 您是在创建实现接口的实例。

接口的关键在于它保证实现它的任何东西都将提供其中声明的方法。

所以现在,使用您的示例,您可以:

MyNiftyClass : IMyInterface
{
    public void CallSomeMethod()
    {
        //Do something nifty
    }
}

MyOddClass : IMyInterface
{
    public void CallSomeMethod()
    {
        //Do something odd
    }
}

现在你有:

IMyInterface nifty = new MyNiftyClass()
IMyInterface odd = new MyOddClass()

现在调用 CallSomeMethod 方法会做一些漂亮或奇怪的事情,当您使用 IMyInterface 作为类型传入时,这将变得特别有用。

public void ThisMethodShowsHowItWorks(IMyInterface someObject)
{
    someObject.CallSomeMethod();
}

现在,根据你是用漂亮的类还是奇怪的类调用上述方法,你会得到不同的行为。

public void AnotherClass()
{
    IMyInterface nifty = new MyNiftyClass()
    IMyInterface odd = new MyOddClass()

    // Pass in the nifty class to do something nifty
    this.ThisMethodShowsHowItWorks(nifty);

    // Pass in the odd class to do something odd
    this.ThisMethodShowsHowItWorks(odd);

}

编辑

这解决了我认为您的预期问题 - 为什么要将变量声明为接口类型?

也就是说,为什么要使用:

IMyInterface foo = new MyConcreteClass();

优先于:

MyConcreteClass foo = new MyConcreteClass();

希望在声明方法签名时使用接口的原因很清楚,但这留下了关于局部范围变量的问题:

public void AMethod()
{
    // Why use this?
    IMyInterface foo = new MyConcreteClass();

    // Why not use this?
    MyConcreteClass bar = new MyConcreteClass();
}

通常没有技术原因为什么首选该界面。我通常使用界面,因为:

  • 我通常会注入依赖项,因此需要多态性
  • 使用界面清楚地表明我打算只使用界面的成员

技术上需要接口的一个地方是使用多态性的地方,例如使用工厂创建变量或(如上所述)使用依赖注入。

从 itowlson 借用一个例子,使用具体声明你不能这样做:

public void AMethod(string input)
{               
    IMyInterface foo;

    if (input == "nifty")
    {
        foo = new MyNiftyClass();
    }
    else
    {
        foo = new MyOddClass();
    }
    foo.CallSomeMethod();
}

【讨论】:

  • 我仍然不明白为什么你会创建一个 IMyInterface 变量而不是仅仅创建 MyNiftyClass nifty = MyNiftyClass()。对我来说是的,接口基本上是一个抽象类,但它没有实现,它只是子类必须实现的必需成员的骨架。那么,当实现在所有实现该接口的 x、y、z 类中时,IMyInterface 的实例如何保存任何内容?我不明白为什么你甚至需要一个 IMyInterface 变量来调用在子类本身中实现的方法
  • "接口的目的是定义几个对象之间的契约,独立于具体实现。"是的,我明白了这一切。这就是为什么您需要在子类中的任何地方使用 IMyInterface var 而不是子类本身的实例!
  • 看签名 public void ThisMethodShowsHowItWorks(IMyInterface someObject) 那个参数是IMyInterface类型的变量。这就是您使用接口变量的原因 - 它们可以采用任何实现该变量的变量
  • 另外,你需要记住IMyInterfaces中DeclarationInstantiation的区别 blah = new MyConcreteClass() 左边是声明,右边是实例化。您正在实例化 MyConcreteClass 的实例,而不是接口的实例。
  • coffeeaddict:“多态性......如果你有一个 IMyInterface someVar 类型的变量,那么基本上......可以为任何实现 IMyInterface 的类型切换,因为该具体类型是 IMyInterface?”确切地。 IMyInterface nifty = new MyNiftyClass() 是人为的:你通常不会费心去写它。但是考虑一下,比如说IMyInterface imi = oddness > 0 ? new OddClass() : new NiftyClass()。我们在编译时不知道我们会得到一个 Odd 还是 Nifty 对象,我们不在乎:我们只关心我们可以将它传递给 TMSHIW,即它是一个IMyInterface。
【解决方案2】:

使用了接口,因此您无需担心哪个类实现了该接口。一个有用的例子是当你有一个工厂方法返回一个具体的实现,具体实现可能会根据你运行的环境而有所不同。它还允许 API 设计者定义 API,同时允许第 3 方在他们认为合适的任何方式。 Sun 使用它的 Java 加密 API 来做到这一点。

public interface Foo {

}

public class FooFactory {
    public static Foo getInstance() {
        if(os == 'Windows') return new WinFoo();
        else if(os == 'OS X') return new MacFoo();
        else return new GenricFoo();
    }
}

您使用工厂的代码只需要了解 Foo,而不需要了解任何具体的实现。

【讨论】:

    【解决方案3】:

    我也处于同样的位置,花了几天时间才弄清楚为什么我们必须使用接口变量。

     IDepartments rep = new DepartmentsImpl();
    

    为什么不

     DepartmentsImpl rep = new DepartmentsImpl();
    

    想象一下,如果一个类实现了两个包含具有相同签名的成员的接口,那么在该类上实现该成员将导致两个接口都使用该成员作为它们的实现。

    class Test 
    {
    static void Main()
    {
        SampleClass sc = new SampleClass();
        IControl ctrl = (IControl)sc;
        ISurface srfc = (ISurface)sc;
    
        // The following lines all call the same method.
        sc.Paint();
        ctrl.Paint();
        srfc.Paint();
    }
    

    }

    interface IControl
    {
      void Paint();
    }
     interface ISurface
    {
      void Paint();
    }
     class SampleClass : IControl, ISurface
     {
       // Both ISurface.Paint and IControl.Paint call this method. 
     public void Paint()
     {
        Console.WriteLine("Paint method in SampleClass");
     }
    

    }

     // Output:
     // Paint method in SampleClass
    // Paint method in SampleClass
    // Paint method in SampleClass
    

    但是,如果两个接口成员不执行相同的功能,则可能导致其中一个或两个接口的实现不正确。

    public class SampleClass : IControl, ISurface
    {
        void IControl.Paint()
        {
            System.Console.WriteLine("IControl.Paint");
        }
        void ISurface.Paint()
        {
            System.Console.WriteLine("ISurface.Paint");
        }
    }
    

    类成员 IControl.Paint 只能通过 IControl 接口使用,ISurface.Paint 只能通过 ISurface 使用。两种方法实现都是独立的,并且都不能直接在类上使用。例如:

       IControl c = new SampleClass();
       ISurface s = new SampleClass();
       s.Paint();
    

    如果我错了,请纠正我,因为我还在学习这个接口概念。

    【讨论】:

      【解决方案4】:

      因为:

      public void ReadItemsList(List<string> items);
      public void ReadItemsArray(string[] items);
      

      可以变成这样:

      public void ReadItems(IEnumerable<string> items);
      

      编辑

      这样想:

      你必须能够做到这一点。

      而不是:

      你必须是这样。

      本质上这是方法和调用者之间的契约。

      【讨论】:

      • 好的,所以你传入了接口类型。为什么?我还是不明白为什么。
      • 我认为你们缺少一个我不理解的基础知识。你假设我知道多态性的所有基础知识。
      • 老兄,你给我一个“它很优雅”的自大回应并没有真正帮助
      • coffeeaddict:考虑未来的课程可能会有所帮助。如果您编写了 ReadItemsList 和 ReadItemsArray 版本,那么今天可能对您有用。但是假设我发明了 MySuperCollection 类,它既不是列表也不是数组——但确实实现了 IEnumerable。在第一种情况下,我不能使用您的 Read 方法,因为您无法预测 MySuperCollection。在接口版本中,我可以将 MySuperCollection 插入到 ReadItems 中,它就可以工作——您不需要更改 ReadItems 或添加新的重载或任何东西。
      • @coffeeaddict @Chaos 想说的是因为 List 和 string[] (一个字符串数组)都实现了接口 IEnumarable 你,或者任何其他代码,不要需要了解每个具体实现,但可以依赖 1 接口 IEnumable 因为我们将接口的合同与具体实现分开
      【解决方案5】:

      假设您有 Boat、Car、Truck、Plane 类。

      这些都有一个共同的方法 TakeMeThere(string destination)

      你会有一个界面:

      public interface ITransportation
      {
          public void TakeMeThere(string destination);
      }
      

      那么你的班级:

      public class Boat : ITransportation
      {
         public void TakeMeThere(string destination) // From ITransportation
         {
             Console.WriteLine("Going to " + destination);
         }
      }
      

      你在这里的意思是,我的班级 Boat 也会完成 ITransportation 告诉我的所有事情。

      然后当您想为运输公司制作软件时。你可以有一个方法

      Void ProvideServiceForClient(ITransportation transportationMethod, string whereTheyWantToGo)
      {
            transportationMethod.TakeMeThere(whereTheyWantToGo); // Cause ITransportation has this method
      }
      

      所以他们想要哪种交通工具并不重要,因为我们知道它可以TakeMeThere

      【讨论】:

      • 谢谢,但我明白了。我没有得到的是在实现该特定接口的子类中创建接口变量。
      【解决方案6】:

      使用接口变量是允许编写处理程序方法的唯一方法,处理程序方法可以接受来自具有不同基类的对象的数据。

      这是任何人都会明白的。

      【讨论】:

      • 来自具有不同基类的对象......这是关于接口的讨论。基类在哪里发挥了作用。
      • 以上是首先要有接口的全部原因。
      【解决方案7】:

      这不是 C# 特有的,所以我建议移到其他标志。 对于你的问题, 我们选择接口的主要原因是提供两个组件之间的协议(可以是 dll、jar 或任何其他组件)。 请参考下文

       public class TestClass
          {
              static void Main()
              {
                  IMyInterface ob1, obj2;
                  ob1 = getIMyInterfaceObj();
                  obj2 = getIMyInterfaceObj();
                  Console.WriteLine(ob1.CallSomeMethod());
                  Console.WriteLine(obj2.CallSomeMethod());
                  Console.ReadLine();
      
              }
      
              private static bool isfirstTime = true;
              private static IMyInterface getIMyInterfaceObj()
              {
                  if (isfirstTime)
                  {
                      isfirstTime = false;
                      return new ImplementingClass1();
                  }
                  else
                  {
                      return new ImplementingClass2();
                  }
              }
          }
          public class ImplementingClass1 : IMyInterface
          {
              public ImplementingClass1()
              {
      
              }
      
      
              #region IMyInterface Members
      
              public bool CallSomeMethod()
              {
                  return true;
              }
      
              #endregion
          }
      
          public class ImplementingClass2 : IMyInterface
          {
              public ImplementingClass2()
              {
      
              }
              #region IMyInterface Members
      
              public bool CallSomeMethod()
              {
                  return false;
              }
      
              #endregion
          }
          public interface IMyInterface
          {
              bool CallSomeMethod();
      
          }
      

      这里的 main 方法不知道类仍然能够使用接口获得不同的行为。

      【讨论】:

        【解决方案8】:

        接口的目的是定义几个对象之间的契约,独立于具体实现。

        所以当你有一个 Intrace ISomething 和一个特定的实现时,你通常会使用它

        class Something : ISomething
        

        所以当你实例化一个合约时,Interface 变量会被使用:

        ISomething myObj = new Something();
        myObj.SomeFunc();
        

        您还应该阅读interface C#

        更新:

        我将通过一个(现实生活中的)示例来解释对变量使用接口而不是类本身的逻辑:

        我有一个通用的存储库接口:

        Interface IRepository {
            void Create();
            void Update();
        }
        

        我有 2 个单独的实现:

        class RepositoryFile : interface IRepository {}
        class RepositoryDB : interface IRepository {}
        

        每个类都有完全不同的内部实现。

        现在我有另一个对象,一个 Logger,它使用一个已经实例化的存储库来进行他的写作。这个对象,不关心Repository是怎么实现的,所以他就实现了:

        void WriteLog(string Log, IRepository oRep);
        

        顺便说一句,这也可以通过使用标准类继承来实现。但是使用接口和类继承之间的区别是另一个讨论。

        有关抽象类和接口之间区别的更详细讨论,请参阅here

        【讨论】:

        • 是的,已经读过了,而且 MSDN 通常在解释更高级的主题方面做得很糟糕,我没有很好地解释为什么你使用 ISampleInterface obj = new ImplementationClass();为什么使用 ISampleInterface 变量来创建该类的新实例?为什么使用Interface 作为变量而不是ImplementationClass obj = new ImplementationClass()。 ImplementationClass 已经满足约定了,为什么还要使用 ISampleInterface 类型的变量???
        • 是的,你的更新。所以这就像你有一个抽象类并且任何子类都是 SomeAbstractClass 类型一样,所以你基本上可以切换一个方法参数,例如参数类型为 SomeAbstractClass 的方法参数,并且基本上将任何继承 SomeAbstractClass 的子类发送到该方法......对吗?
        • 您可能还想阅读以下内容:codeproject.com/KB/cs/abstractsvsinterfaces.aspx
        • 天哪,这就是我所追求的。这里房间里的每个人都假设我完全理解多态性并且我做了很多(抽象类),但是当你将变量、参数或返回类型定义为 IMyInterface你的子类!这里没有人能理解这就是为什么我的大脑不理解发生了什么以及在您的子类中使用基于接口的变量、参数等的意图。当然,我看到了代码,这一切都很好。我只需要非技术性的解释就可以了!!谢谢
        【解决方案9】:

        例如,假设您有两个类:BookNewspaper。您可以阅读其中的每一个,但是这两个从一个公共超类继承是没有意义的。所以他们都会实现IReadable接口:

        public interface IReadable
        {
            public void Read();
        }
        

        现在假设您正在编写一个为用户阅读书籍和报纸的应用程序。用户可以从列表中选择一本书或报纸,然后该项目将被阅读给用户。

        应用程序中读取给用户的方法会将此BookNewspaper 作为参数。这在代码中可能如下所示:

        public static void ReadItem(IReadable item)
        {
            item.Read();
        }
        

        由于参数是IReadable,我们知道该对象有Read()方法,因此我们调用它来读取它给用户。这是BookNewspaper 还是其他任何实现IReadable 的东西都没有关系。各个类通过实现Read() 方法准确地实现了每个项目的读取方式,因为不同的类很可能会有所不同。

        BookRead() 可能如下所示:

        public void Read()
        {
            this.Open();
            this.TurnToPage(1);
            while(!this.AtLastPage)
            {
                ReadText(this.CurrentPage.Text);
                this.TurnPage();
            }
            this.Close();
        }
        

        NewspaperRead() 可能会有点不同:

        public void Read()
        {
            while(!this.OnBackPage)
            {
                foreach(Article article in this.CurrentPage.Articles)
                {
                    ReadText(article.Text);
                }
            }
        }
        

        关键是,接口类型的变量包含的对象保证有一组特定的方法,即使对象的可能类没有以任何其他方式相关。这使您可以编写适用于各种类的代码,这些类具有可以对其执行的通用操作。

        【讨论】:

        • Aaron,你跳过了一些部分,所以我有点迷路了。 “在你的代码中”..在哪里,在什么类中?
        • 我添加了示例代码来解释我在说什么。希望这会有所帮助!
        【解决方案10】:

        不,这是不可能的。设计者没有提供方法。当然,这也是常识。因为接口只包含抽象方法,而抽象方法没有(实现代码的)主体,我们无法创建对象..

        假设即使允许,又有什么用。使用对象调用抽象方法不会产生任何目的,因为没有输出。没有抽象方法的功能。 那么,接口在Java设计和编码中的用途是什么。它们可以用作原型,您可以从中轻松开发新类。它们就像其他类的模板一样实现接口,就像构建建筑物的蓝图一样。

        【讨论】:

          【解决方案11】:

          我相信每个人都在回答使用接口的多态性原因,David Hall 部分地谈到了为什么您会将其作为接口而不是实际的对象名称来引用。当然,仅限于接口成员等是有帮助的,但另一个答案是依赖注入/实例化。

          在设计应用程序时,如果使用依赖注入,它通常会更简洁、更易于管理且更灵活。如果您从未做过,一开始会感觉倒退,但当您开始回溯时,您会希望自己曾经这样做过。

          依赖注入通常通过允许类实例化和控制依赖关系来工作,您只需依赖所需对象的接口即可。

          例子:

          首先对应用程序进行分层。第 1 层逻辑,第 2 层接口,第 3 层依赖注入。 (每个人都有自己的方式,这里只是作秀)。

          在逻辑层你引用接口和依赖层,最后你只基于外部对象的接口创建逻辑。

          我们开始吧:

          public IEmployee GetEmployee(string id)
          {
              IEmployee emp = di.GetInstance<List<IEmployee>>().Where(e => e.Id == id).FirstOrDefault();
          
              emp?.LastAccessTimeStamp = DateTime.Now;
          
              return emp;
          }
          

          请注意上面我们如何使用 di.GetInstance 从依赖项中获取对象。我们在该层的代码永远不会知道或关心 Employee 对象。事实上,如果它在其他代码中发生变化,它永远不会影响我们这里。如果 IEmployee 的接口发生变化,那么我们可能需要进行代码更改。

          关键是,IEmployee emp = 从不真正知道实际对象是什么,但知道接口以及如何使用它。考虑到这一点,这是您想要使用接口而不是对象的时候,因为我们永远不知道或无法访问该对象。

          这是总结..希望对您有所帮助。

          【讨论】:

            【解决方案12】:

            这是面向对象编程中的一个基本概念——多态性。 (wikipedia)

            简短的回答是,通过使用 A 类中的接口,您可以为 A 类IMyInterface 的任何实现。

            这也是松散耦合的一种形式 (wikipedia)——你有很多类,但它们并不显式地相互依赖——只依赖于它们提供的一组属性和方法的抽象概念(界面)。

            【讨论】:

            • 接口是一种按合同编程的形式。从某种角度来看,可以说接口可以用来实现 C# 中的一种形式的多态性,但它们不是多态性。基于接口的编程不限于面向对象语言,相反,面向对象语言不需要接口来运行。
            • 是的,我得到了多态性,主要类型的许多类型......好吧,用抽象类很容易理解。但对我来说,我看到的界面是一些神圣的“东西”,我根本看不到它怎么会有任何状态。
            • (IMyInterface 的任何 A 类实现。) 好的,但是这对我有什么影响,请用蹩脚的术语为我分解。表现得好像我不知道我在用接口做什么,除了我知道它们是一个合同,而且子类必须实现接口定义的任何成员。这部分很简单。
            • 网络、书籍等中缺少一些东西。这显然是在这里推断出来的,我还不知道。这就是我想要理解的。所以...如果您使用 IMyInterface someVar 那么 someVar 代表每个已实现 IMyInterface 的子类?因此,如果 IMyInterface 有方法 myInterfaceMethod 那么这意味着我的 someVar 可以调用 myInterfaceMethod 并且它会选择一些已经实现 myInterfaceMethod 方法的子类?如果是这种情况,那么 someVar 基本上是指向许多子类的多指针!对吗?
            • CoffeeAddict,你是对的,界面本身从来没有任何数据/状态/任何东西。接口只是一个“线索”或“契约”,取决于你如何看待它,以及你使用的是强类型语言还是弱类型语言)到使用它的类,对象实际上被实例化或传入作为参数将具有这些属性这些方法。可以是具有这些属性和方法的任何 类(只要它们声明接口实现),即使该类具有 40 个其他属性和 40 个其他方法(它不应该)。 ...继续
            猜你喜欢
            • 2014-01-16
            • 1970-01-01
            • 2020-09-18
            • 2016-04-05
            • 1970-01-01
            • 1970-01-01
            • 2010-12-23
            • 1970-01-01
            • 2013-12-25
            相关资源
            最近更新 更多