我知道你应该依赖于抽象而不是具体的实现,但我也知道YAGNI原则。我有时会发现自己正在努力调和这两者。
考虑以下类别;
public class Foo
{
public void DoFoo()
{
}
//private foo stuff
}
public class Bar
{
private readonly Foo _foo;
public Bar()
{
_foo = new Foo();
}
}
"酒吧"是我感兴趣的班级;显然有一个问题,Bar正在实例化一个Foo的实例,所以让我重构一下;
public class Bar
{
private readonly Foo _foo;
public Bar(Foo foo)
{
_foo = foo;
}
}
很好,但是Bar的构造函数仍然依赖于Foo,一个具体的实现。我没有获得任何东西(有吗?)。为了解决这个问题,我需要让foo成为一个抽象,这就是我的问题开始的地方。
我发现的每个例子(可以理解)都使用抽象来演示构造函数注入。我只是为了防御性编程,但我认为除了Foo之外我不需要任何其他的实现(测试双打不算数)。创建一个" IFoo"界面或" FooBase"抽象类肯定违反了YAGNI原则?我会为可能的未来情况做点什么,我可以随时做到这一点,例如。
public abstract class Foo
{
public abstract void DoFoo();
//private foo stuff
}
public class Foo1:Foo
{
public override void DoFoo()
{
}
}
这并没有打破Bar,我甚至可以为界面做这个,只要我放弃了"我"惯例(我越来越怀疑)例如。
public interface Foo
{
void DoFoo();
}
public abstract class FooBase:Foo
{
public abstract void DoFoo();
//private foo stuff
}
public class Foo1:FooBase
{
public override void DoFoo()
{
}
}
注入具体实现有什么问题,因为我可以在稍后阶段将它重构为抽象(假设我给抽象提供了与具体实现相同的名称)?
注意:我知道" I"接口命名约定,这不是我的问题。我也知道将Foo作为一个抽象类会破坏代码,无论我以前在哪里实例化它,但假设我广泛使用DI,所以我只需要更改DI容器注册,无论如何我都可能要做的事情。我将介绍一个新的Foo实现。
答案 0 :(得分:4)
但是Bar的构造函数仍然依赖于Foo,一个具体的实现。我没有获得任何东西(有吗?)。
您在此获得的是,当依赖关系Foo
本身获得其自身的依赖关系或需要不同的生活方式时,您可以进行此更改,而无需在{{1}的所有消费者中进行彻底的更改}。
我不需要除Foo之外的任何其他实现(测试双打不算数)
你不能忽略单元测试。在很久以前的Roy Osherove explained,您的测试套件是您的应用程序的另一个(同样重要的)消费者,具有自己的要求。如果添加抽象简化了测试,那么您就不需要其他原因来创建它。
创建一个" IFoo"界面或" FooBase"抽象课肯定违反了YAGNI原则?
如果您为测试创建此抽象,则不会违反YAGNI。在那种情况下YNI(你需要它)。通过不创建抽象,您将在生产代码中进行本地优化。这是局部最优而不是全局最优,因为这种优化不会考虑所有其他(同样重要的)需要维护的代码(即您的测试代码)。
注入具体实现有什么问题,因为我可以将它重构为抽象
每次注入注入具体实例都没有错,尽管如此 - 创建一个抽象可以简化测试。如果它没有简化测试并且让消费者对实现有严格的依赖可能没问题。但要注意,根据具体类型可能会有其缺点。例如,用不同的实例(例如拦截器或装饰器)替换它变得更加困难,而不必对消费者进行更改。如果这不是问题,您也可以使用具体类型。
答案 1 :(得分:2)
正如adaam所说,做你想做的事并没有错。
您使用的DI容器是什么?如果您使用Unity,PRISM有一个很好的例子,说明如何将ViewModel注册为BindableBase(PRISM提供的基类),除非您有一个实现其他接口的基类。
通常我有一个BaseViewModel,它扩展了BindableBase并实现了INotifyDataErrorInfo和其他一些接口。然后,当发现模块时,它们将ViewModel注册为BaseViewModel的类型。
答案 2 :(得分:1)
在Foo
的构造函数中提供Bar
仍然可以提供与在Bar
中实例化相比的内容,即使只是具体的实现。假设您要测试Bar
,并假设您设计Foo
的方式是可能导致单元测试问题的所有功能都放在虚拟方法中。然后在你的单元测试中继承Foo
,覆盖必要的成员,然后将你继承的类的实例传递给Bar
构造函数,如果你在Foo
内实例化Bar
,这显然是不可能的。 {1}}本身。与DI相同的故事 - 您可以在DI容器中将从Foo
继承的类注册为Foo
。