2013-04-16 119 views
26

因此,我正在重新设计我的Android应用程序以使用Dagger。我的应用程序非常庞大复杂,最近遇到以下情况:在构造函数中使用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的多个实例必须构建构造,所以下面做事的匕首的方式,我简单的注入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的构建,直到“金额”被填充。如果我有两个这样的参数,“数量”和“频率”,那么我将有两个调整器,这意味着复杂的检查,以确保那建造重新开始制定者都被称为后,或者我会还添加第三个方法混进去,就像这样:

(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 
     ... 
    } 
} 

即使如此,我不确定的时间,因为我m不知道在我的构造函数被调用之前Dagger是否会注入提供者。

有没有更好的方法?我是否错过了关于匕首是如何工作的?

回答

49

你在说什么就是所谓的辅助注射,目前在任何自动方式下都不支持Dagger。

您可以解决此同工厂模式:当你需要创建一个

class B { 
    @Inject AFactory aFactory; 

    //... 
} 

和:

class AFactory { 
    @Inject DebugLogger debuggLogger; 

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

现在,你可以注入这家工厂,并用它来创建的A实例A以及您在工厂使用的“金额”和“频率”。

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

这允许A具有同时仍然使用注射,以提供记录器实例的记录器,量和频率域的final实例。

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

+0

感谢您的快速回复。你所描述的工厂模式似乎是最好的方法。我也意识到,如果Dagger支持私人领域注入,它很容易让我试图完成。我想知道为什么这不是Dagger原创设计的一部分?我假定匕首使用反射注入。如果是这样,那么私人领域应该没有问题,对吗? – Alex

+0

匕首回落,但这不是它的主要注射方式。它会生成直接设置字段或调用构造函数的代码。因此,它与源代码树中的任何其他代码一样工作,并且无法访问“私人”成员。 –

+0

而一些安全管理人员会打破依赖于类中可访问性的反射。 –

3

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

等待这样的解决方案,杰克沃顿的方法是可取的。

+0

是啊不用担心,我意识到Dagger还处于早期版本。我确实喜欢这些其他功能中的一些正在作为插件来开发,以保持二进制文件的整体大小(对我来说,至少对于Android开发来说,这是一个很有吸引力的选择)。 – Alex

+1

所以提到了自动生成工具:https://github.com/google/auto/tree/master/factory – phazei

3

您遇到了问题,因为您在构造函数中混合了injectables和non injectables。用于注射的一般规则,将节省您吨心痛,并保持你的代码干净是:

  1. 注射剂可以要求在其构造其他注射,而不是newables。

  2. 新成员可以在其构造函数中要求其他newable,但不能用于injectables。

注射剂是服务类型的对象,即做工作的对象,如CreditCardProcessor,MusicPlayer等

Newables是值类型的对象,如信用卡式,歌曲等

0

杰克的帖子是伟大的,但有更简单的方法。 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); 
    } 
}