原文链接:Keeping the Daggers Sharp ⚔️
原文作者:Py ⚔
译文出自:Dimon’s Program Basement
译者:Dimon
Dagger2是一个非常好的依赖注入库,但是其锋利的边缘
处理起来也是比较棘手的。这就让我们来看看Square公司通过遵循哪些最佳事件来防止工程师们伤害自己
!
与直接注入成员变量相比推荐通过构造函数注入
- 直接注入成员变量要求为非
final
字段且非private
字段。
1 2 3 4 5
| class CardConverter { @Inject PublicKeyManager publicKeyManager; @Inject public CardConverter() {} }
|
- 忘记加上
@Inject
会导致NullPointerException
1 2 3 4 5 6
| class CardConverter { @Inject PublicKeyManager publicKeyManager; Analytics analytics; @Inject public CardConverter() {} }
|
- 构造函数注入是更好的方式,因为它允许
不可变
并且保证线程安全
的对象没有部分构造的状态。
1 2 3 4 5 6 7
| class CardConverter { private final PublicKeyManager publicKeyManager; @Inject public CardConverter(PublicKeyManager publicKeyManager) { this.publicKeyManager = publicKeyManager; } }
|
1 2
| class CardConverter @Inject constructor( private val publicKeyManager: PublicKeyManager)
|
- 我们依旧对系统构造的对象使用成员变量注入,比如Android Activities:
1 2 3 4 5 6 7 8 9 10 11
| public class MainActivity extends Activity { public interface Component { void inject(MainActivity activity); } @Inject ToastFactory toastFactory; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Component component = SquareApplication.component(this); component.inject(this); } }
|
单例应该非常罕见
1 2 3 4 5 6 7 8
| @Singleton public class BadgeCounter { public final Observable<Integer> badgeCount; @Inject public BadgeCounter(...) { badgeCount = ... } }
|
1 2 3 4 5 6 7 8 9 10 11
| @Singleton class RealToastFactory implements ToastFactory { private final Application context; @Inject public RealToastFactory(Application context) { this.context = context; } @Override public Toast makeText(int resId, int duration) { return Toast.makeText(context, resId, duration); } }
|
- 在极少数情况下,我们使用作用域来
缓存
创建成本高昂的实例或者重复创建和丢弃的实例。(译者注:这也是为了让你更少的通过Dagger实现单例,因为实现太简单,可能导致滥用单例。)
与@Provides
相比更推荐@Inject
@Provides
方法不应该复制构造函数样板。
- 当所有耦合问题都集中在一个地方的时候,代码更具可读性。
1 2 3 4 5 6 7
| @Module class ToastModule { @Provides RealToastFactory realToastFactory(Application context) { return new RealToastFactory(context); } }
|
- 这对于
单例
尤为重要,这是你在阅读这个类的时候需要了解的关键实现细节(implementation detail)。
1 2 3 4 5
| @Singleton public class BadgeCounter { @Inject public BadgeCounter(...) {} }
|
推荐static
修饰@Provides
方法
- Dagger
@Provides
方法能够static
修饰。
1 2 3 4 5 6 7
| @Module class ToastModule { @Provides static ToastFactory toastFactory(RealToastFactory factory) { return factory; } }
|
- 生成的代码可以直接调用该方法,而不必创建Module实例。该方法调用可以由编译器内联。
1 2 3 4 5 6 7
| @Generated public final class DaggerAppComponent extends AppComponent { @Override public ToastFactory toastFactory() { return ToastModule.toastFactory(realToastFactoryProvider.get()) } }
|
- 一个静态方法不会有太大变化,但所有绑定都是静态的,会导致相当大的性能提升。
- 试你的模块变得抽象并且如果其中一个
@Provides
方法不是静态的,那么在编译时Dagger
会失败。
1 2 3 4 5 6 7
| @Module abstract class ToastModule { @Provides static ToastFactory toastFactory(RealToastFactory factory) { return factory; } }
|
与@Provides
相比更推荐@Binds
- 当您将一种类型映射到另一种类型时,
@Binds
会替换@Provides
。
1 2 3 4 5
| @Module abstract class ToastModule { @Binds abstract ToastFactory toastFactory(RealToastFactory factory); }
|
- 该方法必须是抽象的。它永远不会被调用;生成的代码将知道直接使用该实现。
1 2 3 4 5 6 7 8 9 10 11
| @Generated public final class DaggerAppComponent extends AppComponent { private DaggerAppComponent() { this.toastFactoryProvider = (Provider) realToastFactoryProvider; } @Override public ToastFactory toastFactory() { return toastFactoryProvider.get(); } }
|
避免在接口绑定上使用@Singleton
Statefulness is an implementation detail
- 只有实现才知道他们是否需要确保集中访问的可变状态。
- 将实现绑定到接口时,不应该有任何作用域注释。
1 2 3 4 5 6
| @Module abstract class ToastModule { @Binds @Singleton abstract ToastFactory toastFactory(RealToastFactory factory); }
|
启用error-prone
几个Square团队正在使用它将常见的Dagger错误检查出来。
结论
这些指导原则适用于我们:小型异构团队在大型共享Android代码库上工作。由于您的背景可能不同,因此您应该应用对您的团队最有意义的内容。
轮到你了!您遵循哪些良好做法来保持Dagger代码的清晰度?