注入依赖关系应该在ctor中还是在每个方法中完成?


16

考虑:

public class CtorInjectionExample
{
    public CtorInjectionExample(ISomeRepository SomeRepositoryIn, IOtherRepository OtherRepositoryIn)
    {
        this._someRepository = SomeRepositoryIn;
        this._otherRepository = OtherRepositoryIn;
    }

    public void SomeMethod()
    {
        //use this._someRepository
    }

    public void OtherMethod()
    {
        //use this._otherRepository
    }
}

反对:

public class MethodInjectionExample
{
    public MethodInjectionExample()
    {
    }

    public void SomeMethod(ISomeRepository SomeRepositoryIn)
    {
        //use SomeRepositoryIn
    }

    public void OtherMethod(IOtherRepository OtherRepositoryIn)
    {
        //use OtherRepositoryIn
    }
}

尽管Ctor注入使扩展变得困难(添加新依赖项时调用ctor的任何代码都需要更新),并且方法级别注入似乎仍从类级别依赖项中进行了封装,而我找不到/针对这些方法的任何其他参数。

是否有确定的注射方法?

(注意,我已搜索有关此问题的信息,并试图使该问题变得客观。)


4
如果您的班级具有凝聚力,那么许多不同的方法将使用相同的字段。这应该给您您寻找的答案...
Oded 2012年

@Oded好一点,凝聚力会严重影响决策。例如,如果一个类是辅助方法的集合,每个方法具有不同的依赖关系(看似不具有内聚性,但在逻辑上相似),而另一个具有高度内聚性,则它们应各自使用最有益的方法。但是,这将导致整个解决方案不一致。
StuperUser 2012年

与我提供的示例相关:en.wikipedia.org/wiki/False_dilemma
StuperUser 2012年

Answers:


3

这取决于,如果注入的对象被类中的多个方法使用,并且在每个方法上注入它都没有意义,则需要为每个方法调用解析依赖关系,但是如果依赖关系仅由在构造函数上注入一个方法不是很好,但是由于您正在分配永远无法使用的资源。


15

正确,首先,让我们处理“添加新的依赖项时,任何调用ctor的代码都需要更新”;要明确的是,如果您正在执行依赖项注入,并且在具有依赖项的对象上有任何代码调用new(),那么您做错了

您的DI容器应该能够注入所有相关的依赖项,因此您不必担心更改构造函数签名,因此该参数实际上并不成立。

至于按方法与按类的注入思想,按方法注入有两个主要问题。

一个问题是您的类的方法应该共享依赖关系,这是确保类有效分离的一种方法,如果您看到一个类具有大量依赖关系(可能超过4-5),则该类是主要的候选对象重构为两类。

下一个问题是,为了按方法“注入”依赖项,您必须将它们传递给方法调用。这意味着您必须在方法调用之前解决依赖关系,因此您可能最终会得到一堆这样的代码:

var someDependency = ServiceLocator.Resolve<ISomeDependency>();
var something = classBeingInjected.DoStuff(someDependency);

现在,假设您要在应用程序周围的10个地方调用此方法:您将拥有其中的10个摘要。接下来,假设您需要向DoStuff()添加另一个依赖项:您必须将该代码段更改10次(或将其包装在方法中,在这种情况下,您只是手动复制DI行为,这是浪费的时间)。

因此,您在这里所做的基本工作是使使用DI的类意识到自己的DI容器,这从根本上来说是个坏主意,因为它很快会导致难以维护的笨拙设计。

将其与构造函数注入进行比较;在构造函数注入中,您不受特定的DI容器的束缚,并且您从不直接负责实现类的依赖关系,因此,维护非常轻松。

在我看来,您正在尝试将IoC应用于包含一堆不相关的帮助程序方法的类,而您最好根据使用情况将帮助程序类分为多个服务类,然后使用帮助程序委派电话。这仍然不是一个好方法(帮助程序分类的方法除了处理传递给它们的参数以外,还做其他更复杂的事情通常只是编写不好的服务类),但这至少会使您的设计更加简洁。

(注意,我已经按照您之前建议的方法进行了操作,这是一个非常糟糕的主意,此后我再也没有重复过。结果,我试图分离出确实不需要分离的类,并最终具有一组接口,其中每个方法调用都需要选择几乎固定的其他接口。这是维护的噩梦。)


1
"if you're doing dependency injection and you have any code calling new() on an object with dependencies, you're doing it wrong."如果您要对类中的方法进行单元测试,则必须实例化该方法,还是使用DI来实例化您的被测代码?
StuperUser 2012年

@StuperUser单元测试是一个稍微不同的场景,我的评论仅确实适用于生产代码。当您要通过模拟框架为测试创建模拟(或存根)依赖项时,每个依赖项都具有预配置的行为以及一组假设和断言,因此您需要手动注入它们。
艾德·詹姆斯

1
那就是我说的"any code calling the ctor will need updating"代码,我的生产代码没有调用ctor。由于这些原因。
StuperUser 2012年

2
@StuperUser足够公平,但是您仍将使用所需的依赖项来调用方法,这意味着我的其余答案仍然有效!老实说,从单元测试的角度来看,我同意构造函数注入和强制所有依赖项的问题。我倾向于使用属性注入,因为这允许您仅注入期望用于测试的依赖项,而我发现这基本上是另一组隐式的Assert.WasNotCalled调用,而无需实际编写任何代码!:D
艾德·詹姆斯

3

控件有多种类型,您的问题仅提出两种(您也可以使用factory来注入依赖项)。

答案是:这取决于需要。有时最好使用一种,而另一种使用。

我个人喜欢构造函数注入器,因为构造函数在那里可以完全初始化对象。稍后必须调用setter才能使对象无法完全构造。


3

您错过了至少对我来说最灵活的一种-属性注入。我使用Castle / Windsor,我要做的就是向类中添加一个新的auto属性,并且得到的注入对象没有任何问题且没有破坏任何接口。


我已经习惯了Web应用程序,并且这些类是否位于UI调用的Domain层中,其依赖项已通过与Property Injection类似的方式解决。我想知道如何处理可以传递注入的对象的类。
StuperUser 2012年

我也喜欢属性注入,只是这样,您仍然可以在DI容器中使用普通的构造函数参数,从而自动区分已解析和未解析的依赖项。但是,由于在这种情况下构造函数和属性注入在功能上是相同的,所以我选择不提及它;)
Ed James

属性注入迫使每个对象都是可变的,并以无效状态创建实例。为什么那会更好?
克里斯·皮特曼

2
@Chris实际上,在正常情况下,构造函数和属性注入的行为几乎相同,而对象在解析过程中被完全注入。只有在单元测试期间,才将其视为“无效”,这时您将不会使用DI容器实例化实现。请参阅我对我的答案的评论,以了解为什么这实际上是有益的。我意识到可变性可能是一个问题,但实际上,您将仅通过其接口引用已解析的类,而不会公开要编辑的依赖项。
Ed James
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.