接口的好例子

时间:2009-08-26 15:30:26

标签: c# c#-3.0 interface

我在一家公司工作,有些人要求在我们的代码中使用接口(Visual Studio C#3.5)。

我想要求Iron Clad推理接口是必需的。 (我的目标是证明接口是编程的正常部分。)

我不需要说服,我只需要一个好的论据来用来说服其他人。

我正在寻找的那种论点是基于事实的,而不是基于比较的(即“因为.NET库使用它们”是基于比较的。)

反对他们的论点是:如果一个类被正确设置(使用其公共和私有成员),那么接口只是额外的开销,因为那些使用该类的人仅限于公共成员。如果你需要一个由多于一个类实现的接口,那么只需设置继承/多态。

17 个答案:

答案 0 :(得分:26)

代码解耦。通过对接口进行编程,可以使用接口与实现接口的代码将代码分离。这允许您更改实现,而无需使用它重构所有代码。这与继承/多态相结合,允许您交替使用任何可能的实现。

模拟和单元测试。当方法是虚拟的时,模拟框架最容易使用,默认情况下,这些方法是通过接口获得的。这实际上是我创建界面的最大原因。

定义可能适用于允许它们互换使用的许多不同类的行为,即使类之间没有关系(定义的行为除外)也是如此。例如,Horse和Bicycle类都可以使用Ride方法。您可以定义一个定义Ride行为的接口IRideable,使用此行为的任何类都可以使用Horse或Bicycle对象,而不会强制它们之间的不自然继承。

答案 1 :(得分:18)

  

反对他们的论点是:如果   正确设置一个类(使用它   公共和私人成员)然后   接口只是额外的开销   因为那些使用这个类的人   仅限公众成员。如果你   需要有一个接口   然后由超过1个类实现   只需设置继承/多态。

请考虑以下代码:

interface ICrushable
{
  void Crush();
}

public class Vehicle
{
}

public class Animal
{
}

public class Car : Vehicle, ICrushable
{
  public void Crush()
  {
     Console.WriteLine( "Crrrrrassssh" );
  }
}

public class Gorilla : Animal, ICrushable
{
  public void Crush()
  {
     Console.WriteLine( "Sqqqquuuuish" );
  }
}

建立一个将动物与车辆联系起来的类层次结构是否有任何意义,即使两者都被我的巨型破碎机压碎了?否。

答案 2 :(得分:10)

除了在其他答案中解释的内容之外,interfaces允许您在.NET中模拟多重继承,否则不允许这样做。

唉,有人说

技术由两类人主导:那些理解他们不管理的人,以及管理他们不理解的人。

答案 3 :(得分:6)

启用班级的单元测试。

要有效地跟踪依赖关系(如果未检出并触摸接口,则只能更改类的语义)。

因为没有运行时开销。

启用依赖注入。

...也许是因为它是'2009年的,而不是70年代,而现代语言设计师实际上对他们正在做的事情有所了解?

不应该在每个类接口抛出接口:只是那些对系统至关重要的接口,并且可能会发生重大变化和/或扩展。

答案 4 :(得分:4)

接口和抽象类为不同的东西建模。当你有一个isA关系时,你从一个类派生,所以基类模拟一些具体的东西。当类可以执行一组特定任务时,您可以实现一个接口。

想想一下Serializable的东西,从设计/建模的角度来看,有一个名为Serializable的基类是没有意义的,因为说一些是可序列化的是没有意义的。有一些实现Serializable接口的东西更有意义说'这是类可以做的事情,而不是类是什么'

答案 5 :(得分:3)

您已经获得了一套指导方针,您的老板认为这些指导方针适合您的工作场所和问题领域。因此,为了说服改变这些指导方针,并不是要证明界面通常是好事,而是证明你在工作场所需要它们。

您如何证明您在工作场所编写的代码中需要接口?通过在实际代码库中找到一个位置(不是某些代码来自其他人的产品,当然也不是在某些玩具示例中关于Duck在IAnimal中实现makeNoise方法),其中基于接口的解决方案优于基于继承的解决方案。向你的老板展示你所面临的问题,并询问是否有必要修改指南以适应这种情况。这是一个受教育的时刻,每个人都在看同样的事实,而不是通过一般性和猜测来打击对方。

该准则似乎是由对避免过度工程和过早概括的担忧所驱动的。因此,如果你按照的方式进行论证,我们应该在这里设置一个界面以防将来我们必须...... ,这是好心的,但是对于你的老板来说,它会引发同样的结果 - 工程警报铃首先激发了指导方针。

等到它有一个很好的客观案例,这既适用于您在生产代码中使用的编程技术,也适用于您与经理开始争论的事情。

答案 6 :(得分:3)

接口根本不是“必需的”,这是一个设计决定。我认为你需要说服自己,为什么,根据具体情况,使用接口是有益的,因为添加接口有一个开销。另一方面,为了反对接口的参数,因为你可以“简单地”使用继承:继承有它的缺点,其中之一就是 - 至少在C#和Java中 - 你只能使用一次继承(单继承);但第二个 - 也许更重要 - 是,继承要求你不仅要理解父类的工作方式,还要理解所有的祖先类,这使得扩展更难,但也更脆弱,因为父类的变化'实现可以轻松打破子类。这是GOF书中教给我们的“构成而不是继承”这一论点的关键。

答案 7 :(得分:2)

  • 测试驱动开发
  • 单元测试

如果没有生成解耦代码的接口会很痛苦。最佳实践是针对接口而不是具体实现进行编码。接口看起来很垃圾,但是一旦你发现它们的好处,你就会一直使用它们。

答案 8 :(得分:2)

  • 您可以实现多个接口。你不能从多个类继承。
  • ..就是这样。其他人对代码解耦和测试驱动开发的要点并没有达到问题的关键,因为你也可以用抽象类来做这些事情。

答案 9 :(得分:2)

接口允许您声明一个可以在多种类型(IEnumerable)之间共享的概念,同时允许每个类型都有自己的继承层次结构。

在这种情况下,我们所说的是“这件事可以列举,但这不是它的唯一定义特征”。

接口允许您在定义实施者的功能时做出必要的最小决策。当您创建一个类而不是一个接口时,您已经声明您的概念是纯类的,不适用于结构。在声明类中的成员时,您还可以做出其他决定,例如可见性和虚拟性。

例如,您可以使用所有公共抽象成员创建一个抽象类,并且它非常接近于接口,但是您已经在所有子类中声明该概念是可覆盖的,而您不必将其设置为决定你是否使用了界面。

它们也使单元测试更容易,但我不认为这是一个强有力的论据,因为你可以构建一个没有单元测试的系统(不推荐)。

答案 10 :(得分:1)

如果您的商店正在执行自动化测试,那么接口对依赖注入非常有利,并且能够单独测试一个软件单元。

答案 11 :(得分:1)

因为这不能解答有关接口案例的问题。

但我建议让有关人士阅读..

Head First Design Patterns

- 李

答案 12 :(得分:1)

请提前原谅我的伪代码!

阅读SOLID原则。使用接口的SOLID原则有几个原因。接口允许您解除实现的依赖性。您可以通过使用像StructureMap这样的工具更进一步,以使耦合真正消失。

你可能习惯的地方

Widget widget1 = new Widget;

具体说明你要创建一个新的Widget实例。但是,如果您在另一个对象的方法中执行此操作,您现在说另一个对象直接依赖于Widget的使用。所以我们可以说像

这样的话
public class AnotherObject
{
    public void SomeMethod(Widget widget1)
    {
        //..do something with widget1
    }
}

我们仍然依赖于Widget的使用。但至少这是可测试的,因为我们可以将Widget的实现注入SomeMethod。现在,如果我们使用接口,我们可以进一步解耦。

public class AnotherObject
{
    public void SomeMethod(IWidget widget1)
    {
        //..do something with widget1
    }
}

请注意,我们现在不需要Widget的特定实现,而是要求任何符合IWidget接口的内容。这意味着可以注入任何东西,这意味着在日常使用代码中我们可以注入Widget的实际实现。但这也意味着当我们想要测试这段代码时,我们可以注入假/模拟/存根(取决于你对这些术语的理解)并测试我们的代码。

但我们怎样才能更进一步。通过使用StructureMap,我们可以进一步解耦这些代码。使用最后一个代码示例,我的调用代码看起来像这样

public class AnotherObject
{
    public void SomeMethod(IWidget widget1)
    {
        //..do something with widget1
    }
}

public class CallingObject
{
    public void AnotherMethod()
    {
        IWidget widget1 = new Widget();
        new AnotherObject().SomeMethod(widget1);
    }
}

正如您在上面的代码中看到的,我们通过传入符合IWidget的对象来删除SomeMethod中的依赖项。但是在CallingObject()。AnotherMethod中我们仍然有依赖。我们也可以使用StructureMap来删除这种依赖性!

[PluginFamily("Default")]
public interface IAnotherObject
{
    ...
}

[PluginFamily("Default")]
public interface ICallingObject
{
    ...
}

[Pluggable("Default")]
public class AnotherObject : IAnotherObject
{
    private IWidget _widget;
    public AnotherObject(IWidget widget)
    {
        _widget = widget;
    }

    public void SomeMethod()
    {
        //..do something with _widget
    }
}

[Pluggable("Default")]
public class CallingObject : ICallingObject
{
    public void AnotherMethod()
    {
        ObjectFactory.GetInstance<IAnotherObject>().SomeMethod();
    }
}

请注意,在上面的代码中没有任何地方我们实例化AnotherObject的实际实现。因为StructurMap的所有内容都是有线的,所以我们可以允许StructureMap根据代码运行的时间和地点传递适当的实现。现在代码非常灵活,因为我们可以通过配置或以编程方式指定我们想要使用的实现。这种配置可以在运行中或作为构建过程的一部分等完成。但它不必在任何地方进行硬连接。

答案 13 :(得分:1)

嗯,我的第一反应是,如果你要解释为什么你需要接口,那么无论如何都是一场艰苦的战斗:)

话虽如此,除了上面提到的所有原因外,接口是松散耦合编程的唯一方式,n层架构需要动态更新/替换组件等等 - 在个人经验中也是如此深奥的建筑团队负责人的概念,结果我们生活在dll地狱 - 无论是在.net世界!

答案 14 :(得分:1)

接口声明一个合同,任何实现它的对象都将遵守该合同。这使得确保代码质量比尝试强制执行书面(非代码)或语言结构更容易,当一个类用接口引用装饰时,需求/契约是明确的,代码将不会编译直到你实现该界面完全且类型安全。

使用接口(这里列出)还有很多其他很好的理由,但可能不会与管理产生共鸣,也不会产生良好的,老式的'质量'声明;)

答案 15 :(得分:1)

继承论证的问题在于你要么拥有一个巨大的上帝阶级,要么拥有如此深刻的层次结构,它会使你的头脑旋转。最重要的是,你最终会得到一个你不需要或没有任何意义的课程。

我看到很多“没有多重继承”,虽然这是真的,但它可能不会让您的团队进入阶段,因为您可以拥有多个级别的继承来获得他们想要的东西。

想到了IDisposable实现。您的团队会在Object类上放置一个Dispose方法,让它在系统中传播,无论它是否对某个对象有意义。

答案 16 :(得分:0)

我不明白它的额外开销。

接口提供灵活性,可管理的代码和可重用性。编码到接口时,您无需担心正在使用的特定类的具体实现代码或逻辑。你只是期待一个结果。许多类对同一个特性事物(StreamWriter,StringWriter,XmlWriter)有不同的实现。你不需要担心它们如何实现写作,你只需要调用它。