了解接口

时间:2009-06-25 04:00:29

标签: c# .net asp.net-mvc interface

我仍然无法理解哪些接口有益。我阅读了一些教程,但我仍然不知道它们对于其他什么是“他们让你的课程保持承诺”和“他们帮助多重继承”。

多数民众赞成。我仍然不知道何时甚至在实际工作示例中使用界面,甚至何时确定何时使用界面。

根据我对接口的有限知识,他们可以提供帮助,因为如果某些东西实现了它,那么你可以通过接口允许传入类似不同的类,而不必担心它不是正确的参数。

但是我从来不知道真正的意义是什么,因为他们通常在这一点上停止显示代码在通过界面后会做什么,如果他们这样做,似乎他们没有做任何有用的事情我可以看看并“去哇他们会帮助一个真实世界的例子。”

所以我想我要说的是我正在尝试找到一个真实世界的例子,我可以看到接口在运行。

我也不明白你可以做像这样的对象的引用:

ICalculator myInterface = new JustSomeClass();

所以现在如果我去myInterface点和intellisense会拉起来我只会看到接口方法而不是JustSomeClass中的其他方法。所以我还没有看到这一点。

此外,我开始进行单元测试,他们似乎喜欢使用接口,但我仍然不明白为什么。

例如这个例子:

public AuthenticationController(IFormsAuthentication formsAuth)
{
    FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();
}

public class FormsAuthenticationWrapper : IFormsAuthentication
{
    public void SetAuthCookie(string userName, bool createPersistentCookie)
    {
        FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
    }
    public void SignOut()
    {
        FormsAuthentication.SignOut();
    }
}

public IFormsAuthentication FormsAuth
{
    get;
    set;
}

为什么要打扰这个界面呢?为什么不使用其中的方法制作FormsAuthenticationWrapper并将其称为一天?为什么首先创建一个接口然后让Wrapper实现接口然后最后编写方法?

然后我不明白这句话的真实含义。

就像我现在知道声明说的那样

FormsAuth = formsAuth ?? new FormsAuthenticationWrapper();

如果formsAuth为null,则创建一个新的FormsAuthenticationWrapper,然后将其分配给作为接口的属性。

我想这可以追溯到为什么引用的东西。特别是在这种情况下,因为所有方法都完全相同。 Wrapper没有接口没有的任何新方法,我不确定但是当你这样做时,方法被正确填充(即它们有一个体),它们不会被转换为存根,因为这看起来真的没有意义对我(它将被转换回界面)。

然后在测试文件中他们有:

var formsAuthenticationMock = new Mock<AuthenticationController.IFormsAuthentication>();

所以他们只是传递了FormsAuthentication,我猜的是所有假的存根。我猜测程序实际运行时会使用包装类,因为它有真正的方法可以执行某些操作(例如将某个人签名)。

但是看看新的Mock(来自moq)它会接受一个类或一个接口。为什么不再让封装类将这些方法放入新的Mock调用中呢?

这不只是为你制作存根吗?

9 个答案:

答案 0 :(得分:17)

好的,起初我也很难理解,所以不用担心。

想想看,如果你有一个课程,可以说是一个视频游戏角色。

public class Character
{
}

现在说我希望角色有武器。我可以使用界面来确定武器所需的方法:

interface IWeapon
{
    public Use();
}

所以让我们给角色一个武器:

public class Character
{
    IWeapon weapon;

    public void GiveWeapon(IWeapon weapon)
    {
        this.weapon = weapon;
    }

    public void UseWeapon()
    {
        weapon.Use();
    }
}

现在我们可以创建使用IWeapon接口的武器,我们可以将它们提供给任何角色类,并且该类可以使用该项目。

public class Gun : IWeapon
{
    public void Use()
    {
        Console.Writeline("Weapon Fired");
    }
}

然后你可以把它粘在一起:

Character bob = new character();
Gun pistol = new Gun();
bob.GiveWeapon(pistol);
bob.UseWeapon();

现在这是一个普遍的例子,但它提供了很多力量。如果查找策略模式,您可以阅读更多内容。

答案 1 :(得分:14)

接口定义合同

在您提供的示例中,如果您将??传递给构造函数并且与接口没有任何关系,则null运算符只提供默认值。

更重要的是,您可以使用实际的FormsAuthenticationWrapper对象,但您也可以实现自己的IFormsAuthentication类型,它与包装类完全无关。界面告诉您实现合同需要实现哪些方法和属性,并允许编译器验证您的对象是否真的履行了该合同(在某种程度上 - 在名称中遵守合同很简单,但在精神上却不是这样) ,所以如果你不想,你不必使用预先构建的FormsAuthenticationWrapper。您可以构建一个完全不同但仍然遵守所需合同的不同类。

在这方面,接口很像正常继承,但有一个重要的区别。在C#中,类只能从一种类型继承,但可以实现许多接口。因此,接口允许您在一个类中完成多个合同。一个对象可以一个IFormsAuthentication对象而也是其他东西,比如IEnumerable。

当您从另一个方向查看界面时,界面更有用:它们允许您将许多不同的类型视为完全相同。一个很好的例子是各种集合类。拿这个代码示例:

void OutputValues(string[] values)
{
   foreach (string value in values)
   {
       Console.Writeline(value);
   }
}

这接受一个数组并将其输出到控制台。现在应用这个简单的更改来使用接口:

void OutputValues(IEnumerable<string> values)
{
   foreach (string value in values)
   {
       Console.Writeline(value);
   }
}

此代码仍然接受一个数组并将其输出到控制台。但它也需要List<string>或其他任何你需要的东西来实现IEnumerable<string>。所以我们采用了一个接口,并使用它来创建一个简单的代码块很多更强大。

另一个很好的例子是ASP.Net成员资格提供者。您告诉ASP.Net您通过实现所需的接口来履行成员资格合同。现在,您可以轻松自定义内置的ASP.Net身份验证以使用任何源,这一切都归功于接口。 System.Data命名空间中的数据提供程序以类似的方式工作。

最后一点说明:当我看到一个带有“默认”包装器实现的界面时,我认为它有点像anit-pattern,或者至少是代码味道。它告诉我,可能界面太复杂了,你要么需要拆分它,要么考虑使用组合+事件+委托的一些组合而不是派生来完成同样的事情。

答案 2 :(得分:10)

也许了解接口的最佳方法是使用.NET框架中的一个示例。

考虑以下功能:

void printValues(IEnumerable sequence)
{
    foreach (var value in sequence)
        Console.WriteLine(value);
}

现在我可以写这个函数来接受List<T>object[]或任何其他类型的具体序列。但是因为我已经编写了这个函数来接受类型为IEnumerable的参数,这意味着我可以将任何具体类型传递给实现IEnumerable接口的函数。

这是可取的原因是通过使用接口类型,您的功能比其他方式更灵活。此外,您正在增加此功能的实用性,因为许多不同的呼叫者将能够使用它而无需修改。

通过使用接口类型,您可以将参数的类型声明为您传递的任何具体类型所需的合约。在我的示例中,我不关心您传递给我的类型,我只关心我可以迭代它。

答案 3 :(得分:5)

这里的所有答案都很有帮助,我怀疑我可以添加任何新的东西,但在阅读这里的答案时,两个不同答案中提到的两个概念在我的脑海中确实很好,所以我将构建我的理解希望它对你有所帮助。

类具有方法和属性,类的每个方法和属性都有签名和正文

public int Add(int x, int y)
{
return x + y;
}

Add方法的签名是第一个大括号字符

之前的所有内容
public int Add(int x, int y)

方法签名的目的是为方法指定一个名称,并描述它的保护级别(公共,受保护,内部,私有和/或虚拟),它定义了可以从代码中访问方法的位置

签名还定义了方法返回的值的类型,上面的Add方法返回一个int,以及方法期望调用者传递给它的参数

方法通常被认为是对象可以做的事情,上面的例子暗示了方法定义的类与数字一起工作

方法主体精确地(在代码中)描述对象如何执行方法名称描述的操作。在上面的示例中,add方法的工作原理是将加法运算符应用于它的参数并返回结果。

在语言语法方面,接口和类之间的主要区别之一是接口只能定义methd的签名,而不能定义方法体。

换句话说,接口可以在类的动作(方法)中描述,但它绝不能描述如何执行动作。

现在您希望能够更好地理解界面是什么,我们可以继续讨论问题的第二和第三部分,以及为什么我们会在真实程序中使用界面。

程序中使用的主要时间接口之一是当一个人想要执行某个操作,而不想知道或者与这些操作的执行方式有关时。

这是一个非常抽象的概念,所以也许一个例子可能有助于在你的脑海中巩固事物

想象一下,你是一个非常受欢迎的网络浏览器的作者,每天都有数百万人使用,你有成千上万的功能请求来自人,一些很大,一些很少,一些很好,有些像“带回来<maquee><blink>支持“。

因为您只有少量开发人员,而且当天的工作时间甚至更少,所以您不可能自己实现所有要求的功能,但您仍然希望满足您的客户

因此,您决定允许用户开发自己的插件,这样他们就可以<blink'直到奶牛回家。

要实现这一点,您可能会想出一个类似于以下内容的插件类:

public class Plugin
{
public void Run (PluginHost browser)
{
//do stuff here....
}
}

但是你怎么能合理地实现这个方法呢?你不可能准确地知道每个可能的未来插件将如何工作

一种可能的方法是将Plugin定义为一个接口,让浏览器使用它来引用每个插件,如下所示:

public interface IPlugin
{
void Run(PluginHost browser);
}

public class PluginHost
{
public void RunPlugins (IPlugin[] plugins)
{
foreach plugin in plugins
{
plugin.Run(this);
}
}
}

请注意,如前所述,IPlugin接口描述了Run方法,但未指定Run如何工作,因为这是特定于每个插件的,我们不希望插件主机关注每个插件的细节。 / p>

为了演示类和接口之间关系的“可以成为”方面,我将为下面的插件主机编写一个实现<blink>标记的插件。

public class BlinkPlugin: IPlugin
{
private void MakeTextBlink(string text)
{
//code to make text blink.
}
public void Run(PluginHost browser)
{
MakeTextBlink(browser.CurrentPage.ParsedHtml);
}
}

从这个角度来看,你可以看到插件是在一个名为BlinkPlugin的类中定义的,但是因为它也实现了IPlugin接口,它也可以被称为IPlugin对象,就像上面的PluginHost类那样,因为它没有知道或关心这个类实际上是什么类型,只是它可以是一个IPlugin

我希望这有所帮助,我真的不打算这么久。

答案 4 :(得分:2)

我将在下面举一个例子,但让我先谈谈你的一个陈述。 “我不知道如何识别何时使用”。把它放在边缘。您不需要确定何时使用它,但何时不使用它。任何参数(至少对公共方法),任何(公共)属性(以及我个人实际上将列表扩展到其他任何东西)应该被声明为接口而不是特定类。我唯一没有声明特定类型的东西就是没有合适的界面。

我要去

IEnumerable<T> sequence;

什么时候我能够而且几乎没有(我能想到的唯一情况就是如果我真的需要ForEach方法)

List<T> sequence;

现在是一个例子。假设您正在构建一个可以比较汽车和计算机价格的系统。每个都显示在列表中。

汽车价格来自一组网站和一组服务的计算机价格。

解决方案可能是: 创建一个网页,比如使用数据网格和IDataRetriever的依赖注入 (其中IDataRetriver是一些可以获取数据的界面,您无需知道数据来自何处(数据库,XML,Web服务或......)或如何获取数据(数据挖掘,SQL内部数据查询或从中读取数据)文件)。

由于这两个场景我们除了共同使用外什么都没有,超级类别没什么意义。但是使用我们的两个类(一个用于汽车,一个用于计算机)的页面需要在两种情况下执行完全相同的操作,以使我们需要告诉页面(编译器)哪些操作是可能的。我们通过接口来实现,然后这两个类实现了这个接口。

使用依赖注入与何时或如何使用接口无关,但我包含它的原因是另一种常见的场景,其中接口使您的生活更轻松。测试。如果使用注入和接口,则可以在测试时轻松地将生产类替换为测试类。 (这可能是切换数据存储或强制执行在发布代码中可能非常难以产生的错误,比如竞争条件)

答案 5 :(得分:1)

我们使用接口(或抽象基类)来允许多态,这是面向对象编程中非常核心的概念。它允许我们以非常灵活的方式组合行为。如果您还没有,请阅读Design Patterns - 它包含许多使用接口的示例。

关于Test Doubles(例如Mock对象),我们使用接口来删除我们当前不想测试的功能,或者在单元测试框架内无法工作的功能。

特别是在使用Web开发时,当代码在Web上下文之外执行时,我们依赖的许多服务(例如HTTP上下文)不可用,但如果我们在接口后面隐藏该功能,我们可以在测试期间用其他东西替换它。

答案 6 :(得分:1)

我理解的方式是:

派生是'is-a'关系,例如,A Dog是Animal,A Cow是Animal,但是从不派生界面,它被实现。

因此,界面是一种“可以”的关系,例如,A Dog可以是间谍犬,A Dog可以是Circus Dog等。但要实现这一点,狗必须学习一些特定的东西。在OO术语中,如果它实现了一个接口,那么你的类必须能够做一些特定的事情(他们称之为契约)。例如,如果你的类实现了IEnumerable,它显然意味着你的类具有(必须)这样的功能,它的对象可以是Enumerated。

因此,从本质上讲,通过接口实现,一个类向用户公开了一个功能,即它可以做某事而且它不是继承。

答案 7 :(得分:1)

几乎所有关于界面的内容都让我有机会。

简单来说,界面是两个或两个以上相关的东西,否则就是非相关的类。

接口定义契约,确保任何两个或多个类(即使不完全相关)碰巧实现公共接口,将包含一组通用操作。

结合多态性的支持,可以使用接口编写更清晰和动态的代码。

例如

界面livingBeings

- 说()//说任何生活的人都需要定义他们说话的方式

类狗实施livingBeings

- speak(){bark;} //作为狗说话的实现

班鸟实施livingBeings

- speak(){chirp;} //实施说话为鸟

答案 8 :(得分:0)

ICalculator myInterface = new JustSomeClass();
JustSomeClass myObject = (JustSomeClass) myInterface;

现在你有两个“接口”可以处理对象。

我对此也很陌生,但我喜欢将接口视为遥控器上的按钮。使用ICalculator接口时,您只能访问界面设计人员所需的按钮(功能)。使用JustSomeClass对象引用时,您有另一组按钮。但他们都指向同一个对象。

这样做的原因有很多。对我来说最有用的是同事之间的沟通。如果他们可以就接口(将被按下的按钮)达成一致,那么一个开发人员可以实现按钮的功能,另一个可以编写使用按钮的代码。

希望这有帮助。