使用Dagger在构造函数上进行依赖注入

时间:2013-04-16 14:45:54

标签: android dependency-injection dagger

所以,我正在重新设计我的Android应用程序以使用Dagger。我的应用程序庞大而复杂,我最近遇到了以下情况:

对象A需要一个特殊的DebugLogger实例,它是注入的理想选择。我可以通过A的构造函数注入它,而不是绕过记录器。这看起来像这样:

class A
{
    private DebugLogger logger;

    @Inject
    public A(DebugLogger logger)
    {
        this.logger = logger;
    }

    // Additional methods of A follow, etc.
}

到目前为止这是有道理的。但是,A需要由另一个类B构造。必须构造A的多个实例,因此遵循Dagger的做事方式,我简单地将一个Provider<A>注入B:

class B
{
    private Provider<A> aFactory;

    @Inject
    public B(Provider<A> aFactory)
    {
        this.aFactory = aFactory;
    }
}

好的,到目前为止还不错。但等等,突然A需要额外的输入,例如一个称为“数量”的整数,这对于它的构造至关重要。现在,我的A构造函数需要如下所示:

@Inject
public A(DebugLogger logger, int amount)
{
...
}

突然,这个新参数会干扰注射。而且,即使这确实有效,除非我弄错了,否则当从提供者检索新实例时,我无法传递“金额”。我可以在这里做几件事,我的问题是哪一个最好?

我可以通过添加一个预期在构造函数之后调用的setAmount()方法来重构A.然而,这是丑陋的,因为它迫使我延迟A的构造直到“数量”被填入。如果我有两个这样的参数,“数量”和“频率”,那么我将有两个安装者,这将意味着要么复杂的检查以确保在调用两个setter之后恢复构造A,或者我将不得不在混合中添加第三个方法,如下所示:

(Somewhere in B):

A inst = aFactory.get();
inst.setAmount(5);
inst.setFrequency(7);
inst.doConstructionThatRequiresAmountAndFrequency();

另一种选择是我不使用基于构造函数的注射并使用基于场的注射。但现在,我必须把我的田地公之于众。这对我来说并不合适,因为现在我有义务将我班级的内部数据透露给其他班级。

到目前为止,我能想到的唯一优雅的解决方案是为提供商使用基于字段的注入,如下所示:

class A
{
    @Inject
    public Provider<DebugLogger> loggerProvider;
    private DebugLogger logger;

    public A(int amount, int frequency)
    {
        logger = loggerProvider.get();
        // Do fancy things with amount and frequency here
        ...
    }
}

即便如此,我也不确定时间,因为我不确定Dagger是否会在调用构造函数之前注入提供者。

有更好的方法吗?我只是错过了Dagger的工作原理吗?

5 个答案:

答案 0 :(得分:53)

您所说的是辅助注射,Dagger目前不支持任何自动方式。

您可以使用工厂模式解决此问题:

class AFactory {
  @Inject DebugLogger debuggLogger;

  public A create(int amount, int frequency) {
    return new A(debuggLogger, amount);
  }
}

现在您可以注入此工厂并使用它来创建A的实例:

class B {
  @Inject AFactory aFactory;

  //...
}

当您需要使用“金额”和“频率”创建A时,您可以使用工厂。

A a = aFactory.create(amount, frequency);

这允许A拥有记录器,数量和频率字段的final个实例,同时仍然使用注入来提供记录器实例。

Guice有一个辅助注射插件,它可以为您自动创建这些工厂。 Dagger邮件列表上有have been discussion关于添加它们的适当方式,但截至本文撰写时尚无任何决定。

答案 1 :(得分:3)

杰克的帖子所说的完全正确。也就是说,我们(一些与Guice和Dagger一起工作的谷歌人)正在开发一种替代版本的“辅助注射”或自动工厂生产,它可以由Guice或Dagger使用,也可以单独使用 - 也就是说,它将为您生成工厂类源代码。这些工厂类将(如果适用)可注入任何标准JSR-330类。但它还没有发布。

在这样的解决方案之前,Jake Wharton的方法是明智的。

答案 2 :(得分:3)

您遇到问题,因为您在构造函数中混合了注射剂和非注射剂。注射的一般规则将为您节省大量心痛并保持代码清洁:

  1. 注射器可以在其构造函数中请求其他注射剂,但不能用于新手。

  2. Newables可以在他们的构造函数中请求其他newables,但不能用于注射剂。

  3. Injectables是服务类型对象,即可以工作的对象,如CreditCardProcessor,MusicPlayer等。

    Newables是值类型对象,如CreditCard,Song等。

答案 3 :(得分:2)

杰克的帖子很棒,但还有更简单的方法。 Google创建了AutoFactory库,用于在编译时自动创建工厂。

首先,使用A注释和注入参数的@AutoFactory注释创建类@Provided

@AutoFactory
public class A {

    private DebugLogger logger;

    public A(@Provided DebugLogger logger, int amount, int frequency) {
        this.logger = logger;
    }
}

然后库在编译时创建AFactory类。因此,您只需将工厂注入B类的构造函数即可。

public class B {

    private final AFactory aFactory;

    @Inject
    public B(AFactory aFactory) {
        this.aFactory = aFactory;
    }

    public A createA(int amount, int frequency) {
        return aFactory.create(amount, frequency);
    }
}

答案 4 :(得分:0)

  

我只想补充一点,在此问题发布之后已经过去了几年,现在有一个名为   AssistedInject由Jake和Square的朋友创建,   解决完全相同的问题,并且与Dagger 2完全兼容。

您可以在这里找到它:https://github.com/square/AssistedInject