29

所以,我目前正在重新设计我的一个 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的做事方式,我简单地将aProvider<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 工作原理的一些信息吗?

4

5 回答 5

55

你所说的被称为辅助注射,目前 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);

这允许在仍然使用注入来提供记录器实例的同时拥有记录器、数量和频率字段的实例Afinal

Guice 有一个辅助注入插件,它基本上可以为您自动创建这些工厂。Dagger 邮件列表上已经讨论了添加它们的适当方式,但在撰写本文时尚未做出任何决定。

于 2013-04-16T15:24:26.993 回答
3

杰克的帖子说的是完全正确的。也就是说,我们(一些与 Guice 和 Dagger 合作的 Google 人员)正在研究“辅助注入”或自动工厂生成的替代版本,它应该可由 Guice 或 Dagger 使用或独立使用——也就是说,它将为您生成工厂类源代码。这些工厂类(如果合适)将像任何标准 JSR-330 类一样可注入。但它还没有发布。

等待这样的解决方案,Jake Wharton 的方法是可取的。

于 2013-04-16T23:16:29.823 回答
3

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

  1. Injectables 可以在它们的构造函数中请求其他的 injectables,但不能请求 newables。

  2. Newables 可以在其构造函数中请求其他 newables,但不能请求可注入对象。

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

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

于 2013-11-14T15:17:25.600 回答
2

Jake 的帖子很棒,但还有更简单的方法。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);
    }
}
于 2017-08-19T18:45:32.497 回答
0

我只是想补充一下,在这个问题发布后的几年里,现在有一个名为的库 AssistedInject,它是由 Square 的 Jake 和朋友创建的,用于解决完全相同的问题,并且与 Dagger 2 完全兼容。

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

于 2019-04-10T20:24:48.103 回答