我在一家公司工作,有些人要求在我们的代码中使用接口(Visual Studio C#3.5)。
我想要求Iron Clad推理接口是必需的。 (我的目标是证明接口是编程的正常部分。)
我不需要说服,我只需要一个好的论据来用来说服其他人。
我正在寻找的那种论点是基于事实的,而不是基于比较的(即“因为.NET库使用它们”是基于比较的。)
反对他们的论点是:如果一个类被正确设置(使用其公共和私有成员),那么接口只是额外的开销,因为那些使用该类的人仅限于公共成员。如果你需要一个由多于一个类实现的接口,那么只需设置继承/多态。
答案 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)
答案 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)有不同的实现。你不需要担心它们如何实现写作,你只需要调用它。