繁体   English   中英

了解接口

[英]Understanding Interfaces

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

多数民众赞成。 我仍然不知道什么时候甚至可以在实际工作示例中使用接口,甚至何时确定何时使用它。

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

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

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

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

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,然后将其分配给作为Interface的属性。

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

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

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

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

但是看一下新的Mock(来自moq)它会接受一个类或一个接口。 为什么不再让包装类把那些方法放进然后在新的Mock调用中呢?

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

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

想一想,如果你有一个课程,那就说是一个视频游戏角色。

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

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

接口定义合同

在你提供的例子中, ?? 如果将null传递给构造函数并且实际上与接口没有任何关系,则operator只提供一个默认值。

更相关的是您可能使用实际的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,或者至少是代码味道。 它告诉我,可能界面太复杂了,你要么需要拆分它,要么考虑使用组合+事件+委托的一些组合而不是派生来完成同样的事情。

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

考虑以下功能:

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

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

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

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

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

类具有方法和属性,类的每个方法和属性都具有签名和主体

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如何工作,因为这是特定于每个插件的,我们不希望插件主机关注每个插件的细节。

为了演示类和接口之间关系的“可以是”方面,我将为下面的插件主机编写一个实现<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

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

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

我走了

IEnumerable<T> sequence;

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

List<T> sequence;

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

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

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

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

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

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

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

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

我理解的方式是:

推导是'is-a'关系,例如,A Dog是Animal,A Cow是Animal,但接口永远不会派生,它是实现的。

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

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

几乎所有关于接口的内容都让我有机会。

简单来说,接口是两个或更多相关的东西,否则就是非相关的类。

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

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

例如。

界面livingBeings

-说话()//表示,任何人谁 livingBeing需要确定他们如何说话

class dog实现livingBeings

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

class bird实现了livingBeings

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

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

现在,您可以使用两个“接口”来处理对象。

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

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

希望这可以帮助。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM