对接口进行编码远远超出了测试代码的能力。它在代码中创造了灵活性,允许根据产品需求换入或换出不同的实现。
依赖注入是编写接口代码的另一个好理由。
如果我们有一个名为 Foo 的对象,它被十个客户使用,现在客户 x 希望让 Foo 以不同的方式工作。如果我们已经编码到一个接口(@987654321@),我们只需要根据CustomFoo 中的新要求实现IFoo。只要我们不更改IFoo,就不需要太多。客户 x 可以使用新的CustomFoo,其他客户可以继续使用旧的 Foo,并且需要进行一些其他代码更改来适应。
但我真正想说的是,接口可以帮助消除循环引用。如果我们有一个对象 X 依赖于对象 Y 并且对象 Y 依赖于对象 X。我们有两个选择 1. 对象 x 和 y 必须在同一个程序集中,或者 2. 我们必须找到一些方法打破循环引用。我们可以通过共享接口而不是共享实现来做到这一点。
/* Monolithic assembly */
public class Foo
{
IEnumerable <Bar> _bars;
public void Qux()
{
foreach (var bar in _bars)
{
bar.Baz();
}
}
/* rest of the implmentation of Foo */
}
public class Bar
{
Foo _parent;
public void Baz()
{
/* do something here */
}
/* rest of the implementation of Bar */
}
如果 foo 和 bar 具有完全不同的用途和依赖关系,我们可能不希望它们在同一个程序集中,特别是如果该程序集已经很大。
为此,我们可以在其中一个类上创建一个接口,例如Foo,并引用Bar 中的接口。现在我们可以将接口放在Foo 和Bar 共享的第三个程序集中。
/* Shared Foo Assembly */
public interface IFoo
{
void Qux();
}
/* Shared Bar Assembly (could be the same as the Shared Foo assembly in some cases) */
public interface IBar
{
void Baz();
}
/* Foo Assembly */
public class Foo:IFoo
{
IEnumerable <IBar> _bars;
public void Qux()
{
foreach (var bar in _bars)
{
bar.Baz();
}
}
/* rest of the implementation of Foo */
}
/* Bar assembly */
public class Bar:IBar
{
IFoo _parent;
/* rest of the implementation of Bar */
public void Baz()
{
/* do something here */
}
我认为还有一个论点是维护接口与其实现分开,并在发布周期中以明显不同的方式对待它们,因为这允许并非全部针对相同源编译的组件之间的互操作性。如果完全编码到接口并且如果接口只能针对主要版本增量而不是次要版本增量进行更改,那么相同主要版本的任何组件组件都应该与同一主要版本的任何其他组件一起使用,而不管次要版本如何。
通过这种方式,您可以拥有一个发布周期较慢的库项目,其中仅包含接口、枚举和异常。