在C#中使用接口有什么好处?

时间:2009-06-23 22:36:42

标签: c# oop interface design-patterns

几年前,我被迫进入一个工作中的软件项目,并被迫快速学习C#。我的编程背景很弱(经典ASP)。

这些年来我学到了很多东西,但由于我学习C#的强制性,我不清楚有很多基本概念。

具体来说,是一个界面。我理解基础知识,但在编写应用程序时,我很难弄清楚它的实际用途。为什么要为他们的应用程序编写接口?

由于 凯文

10 个答案:

答案 0 :(得分:16)

界面说明了某些事情应该如何运作。将其视为合同或模板。它是Inverson of Control或Dependancy Injection等事情的关键。

我使用Structure Map作为我的IoC容器。这允许我为我的所有类定义一个接口。你可能会说的地方

Widget w = new Widget();

我会说

IWidget w = ObjectFactory.GetInstance<IWidget>();

这是非常强大的,因为我的代码并不一定说Widget确实是什么。它只知道Widget可以根据IWidget的界面做什么。

这有一些强大的功能,因为现在我使用的是IoC容器,我可以做一些更好的事情。在我需要使用Widget的单元测试中,我可以为Widget创建一个模拟器。所以说我的Widget通过连接到数据库或Web服务来做一些非常强大的功能,我的模拟可以模拟连接到这些资源并返回给我的存根数据。这使我的测试运行得更快,并且行为更加可靠。因为我正在使用StructureMap,所以我可以告诉StructureMap在我的代码的生产使用期间加载我的Widget的实际实现,并在编程期间以编程或配置的方式加载Widget的模拟版本。

另外,因为我使用的是IoC容器,所以我可以为我的应用程序提供很酷的新功能,例如编写三种不同的缓存数据的方法。我可以使用Lucene.NET等工具为本地缓存创建一个本地开发人员框缓存。我可以让开发服务器使用.NET缓存,它可以在一个盒子上运行。然后我可以为生产服务器提供第三个选项,使用缓存层,如MemCache Win32或Velocity。只要所有三个缓存实现都符合相同的接口,它们的实际实现根本不涉及我(或我的代码)。我只是要求StructureMap获取当前的环境实现,然后继续工作。

如果您完全遵循依赖注入,那么接口在这里也可以使用诸如StructureMap之类的IoC容器,因为我可以在我的类的构造函数中通过接口声明类的使用。

public class Widget(IWidgetRepository repository, IWidgetService service) : IWidget
{
    //do something here using my repository and service
}

然后当我通过StructureMap创建一个Widget实例时,比如这个

IWidget widget = ObjectFactory.GetInstance<IWidget>();

请注意,我没有在构造函数中指定存储库或服务。 StructureMap通过构造函数中指定的接口知道如何获取适当的实例并将其传递给它们。这使得代码非常强大和干净!

全部来自Interfaces的简单定义及其巧妙使用!

答案 1 :(得分:7)

基本情况是“IWriter”案例。

假设您正在创建一个可以写入控制台的类,它具有各种有用的函数,如write()和peek()。

然后,您想编写一个可以写入打印机的类,因此您不必重新创建新类,而是使用IWriter接口。

现在关于界面很酷的事情是你可以编写所有的编写代码,而不必事先知道你的写作目标是什么,然后当用户决定(在运行时)天气他想要写入控制台或打印机时,您只需将对象定义为控制台/打印机编写器,您无需更改编写代码中的任何内容,因为它们都使用相同的前端(接口)。

答案 2 :(得分:6)

一个简单的答案:使用接口对合同而不是实现进行编程。

这怎么可能有帮助?开始使用接口会(希望)让你习惯于更松散地耦合类。当您针对自己的具体类进行编码时,很容易开始调用数据结构而不必严格分离关注点。你最终得到的课程“知道”关于其他课程的一切,事情会变得非常混乱。通过将自己限制为接口,您只能确保它满足接口的合同。它注入了一种有时有助于紧密耦合的摩擦力。

答案 3 :(得分:1)

一个例子。考虑一个显示报告的MDI应用程序,基本上有两种不同的报告类型。图表和网格。我需要将这些报告保存为PDF,我需要将它们邮寄给某人。 用户单击以将报告保存为PDF的菜单的事件处理程序可以执行此操作:

void ExportPDF_Clicked(...) {
   if(currentDocument is ChartReport) {
      ChartReport r = currentDocument as ChartReport;
      r.SavePDF();
   } else if(currentDocument is GridReport) {
     GridReport r = currentDocument as GridReport;
      r.SavePDF();
   }
}

我宁愿让我的ChartReport和GridReport实现这个界面:

public interface Report {
  void MailTo();
  void SavePDF();
}

现在我能做到:

void ExportPDF_Clicked(...) {
   Report r = currentDocument as Report;
   r.SavePDF();
}

类似于需要在不同报告类型上执行相同操作(将其保存到文件,放大,打印等)的其他代码。 上面的代码在下周我添加PivotTableReport也会对Rpoert产生影响时仍能正常工作。

答案 4 :(得分:1)

上面已经提到了IOC和依赖注入,我建议你看看它们。

但是,很大程度上,接口允许为不需要继承模型的对象指定合同。

假设我有类Foo,它有函数x和y以及属性z,我在它周围构建代码。

如果我发现一个更好的方法来做Foo,或者另一种Foo需要实现,我当然可以将一个基本的Foo类扩展到FooA,FooB,MyFoo等,但这需要所有的Foo都有相同的核心功能,或者确实,任何未来的Foo创建者都可以访问基础Foo类并了解其内部工作原理。在C#中,这意味着未来的Foos除了Foo之外不能继承任何其他东西,因为C#不支持多重继承。

这还需要我了解Foo未来的可能状态,并尽量不要在我的基础Foo类中禁止它们。

使用接口IFoo简单地说明了一个类在我的Foo框架中工作所需的“契约”,并且我不关心任何未来的Foo类可以从内部继承或看起来像内部,只要它们有fn x fn y和z。它使框架更加灵活,并且对未来的添加开放。

但是,如果Foo需要在其基础上运行大量的核心,而这种核心在合同场景中不适用,那就是您希望继承。

答案 5 :(得分:0)

好文章。

接口是一种合同,它向客户端保证类或结构的行为方式。

http://www.codeguru.com/csharp/csharp/cs_syntax/interfaces/article.php/c7563

这可能是解释我遇到过的最简单明了的方法:

“答案是,当你不知道将提前传递的特定对象类型时,它们提供了一种相当类型安全的方法来构建接受对象的例程。你唯一知道的对象是将被传递给您的例程的是,他们有特定的成员必须在您的日常工作中才能使用该对象。 我可以给出接口需求的最好例子是在团队环境中。接口有助于定义不同组件之间的通信方式。通过使用接口,您可以消除开发人员错误解释他们必须添加到某个类型的成员或者如何调用定义接口的其他类型的可能性。没有接口,错误会蔓延到系统中,并且在运行时很难显示,而且很难找到。使用接口时,在编译时会立即捕获定义类型的错误,其中成本要低得多。“

答案 6 :(得分:0)

这是一个book,它讲述了所有关于接口的内容。它促进了接口属于客户端的概念,即调用者。这是一个很好的概念。如果你只需要你要调用的东西 - 例如 - count()和get(),那么你可以定义这样的接口并让类实现这些函数。有些类会有许多其他功能,但你只对这两个类感兴趣 - 所以你需要更少了解你正在使用的类。只要他们满足合同,你就可以使用它们。

答案 7 :(得分:0)

一些事情,当您从接口继承时,它会强制您实现接口中定义的所有方法。另一方面,这也是引入常规类不支持的多重继承的好方法。 http://msdn.microsoft.com/en-us/library/ms173156.aspx

答案 8 :(得分:0)

基于第一原则的简单答案:

一个程序是一个拥有自己的形而上学(代码的现实/物质/东西)和认识论(你可以知道/相信/推理代码)的宇宙。一种好的编程语言试图最大化形而上学的灵活性(让你轻松地制作东西),同时确保认知的严谨性(确保你的宇宙在内部是一致的)。

因此,将实现继承视为一个形而上学的构建块(构成您的小代码世界的东西)和接口继承作为一种认知约束(它允许您相信您的代码)。

当您只想确保自己可以相信时,可以使用界面。大部分时间都是你需要的。

答案 9 :(得分:0)

你提到很难找到接口的实际用途..我发现它们在构建可扩展应用程序时会自成一体,例如基于插件的应用程序,其中第三方插件必须符合特定规则。这些规则可以由接口定义。

你可以这样做,以便在加载插件时,它必须有一个Init方法,它接受一个实现IServices接口的类。

public interface IServices
{
    DataManager Data { get; set; }
    LogManager Log { get; set; }
    SomeOtherManager SomeOther { get; set; }
}

public class MrPlugin
{
    public void Init(IServices services)
    {
        // Do stuff with services
    }
}

所以..如果你有一个实现IServices接口的类,然后你实例化它一次,你可以在初始化时将它传递给所有的插件,他们可以使用你在接口中定义的任何服务。