原文链接:Keeping the Daggers Sharp ⚔️
原文作者:Py ⚔
译者:Dimon
Dagger2是一个非常号的依赖注入库,但是其锋利的边缘
处理起来也是比较棘手的。这就让我们来看看Square公司通过遵循哪些最佳事件来防止工程师们伤害自己
!
与直接注入成员变量相比推荐通过构造函数注入
-
直接注入成员变量要求为非
final
字段且非private
字段。// BAD class CardConverter { @Inject PublicKeyManager publicKeyManager; @Inject public CardConverter() {} }
-
忘记加上
@Inject
会导致NullPointerException
// BAD class CardConverter { @Inject PublicKeyManager publicKeyManager; Analytics analytics; // Oops, forgot to @Inject @Inject public CardConverter() {} }
-
构造函数注入是更好的方式,因为它允许
不可变
并且保证线程安全
的对象没有部分构造的状态。// GOOD class CardConverter { private final PublicKeyManager publicKeyManager; @Inject public CardConverter(PublicKeyManager publicKeyManager) { this.publicKeyManager = publicKeyManager; } }
-
Kotlin消除了构造函数的注入样板:
class CardConverter @Inject constructor( private val publicKeyManager: PublicKeyManager )
-
我们依旧对系统构造的对象使用成员变量注入,比如Android Activities:
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); } }
单例应该非常罕见
-
当我们需要集中访问可变状态时,单例很有用。
// GOOD @Singleton public class BadgeCounter { public final Observable<Integer> badgeCount; @Inject public BadgeCounter(...) { badgeCount = ... } }
-
如果一个对象没有可变状态时,则不需要是单例。
// BAD, should not be a singleton! @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
方法不应该复制构造函数样板。-
当所有耦合问题都集中在一个地方的时候,代码更具可读性。
@Module class ToastModule { // BAD, remove this binding and add @Inject to RealToastFactory @Provides RealToastFactory realToastFactory(Application context) { return new RealToastFactory(context); } }
-
这对于
单例
尤为重要,这是你在阅读这个类的时候需要了解的关键实现细节(implementation detail)。// GOOD, I have all the details I need in one place. @Singleton public class BadgeCounter { @Inject public BadgeCounter(...) {} }
推荐static
修饰@Provides
方法
-
Dagger
@Provides
方法能够statis
修饰。@Module class ToastModule { @Provides static ToastFactory toastFactory(RealToastFactory factory) { return factory; } }
-
生成的代码可以直接调用该方法,而不必创建Modle实例。该方法调用可以由编译器内联。
@Generated public final class DaggerAppComponent extends AppComponent { // ... @Override public ToastFactory toastFactory() { return ToastModule.toastFactory(realToastFactoryProvider.get()) } }
-
一个静态方法不会有太大变化,但所有绑定都是静态的,会导致相当大的性能提升。
-
试你的模块变得抽象并且如果其中一个
@Provides
方法不是静态的,那么在编译时Dagger
会失败。@Module abstract class ToastModule { @Provides static ToastFactory toastFactory(RealToastFactory factory) { return factory; } }
与@Provides
相比更推荐@Binds
-
当您将一种类型映射到另一种类型时,
@ Binds
会替换@Provides
。@Module abstract class ToastModule { @Binds abstract ToastFactory toastFactory(RealToastFactory factory); }
-
该方法必须是抽象的。它永远不会被调用;生成的代码将知道直接使用该实现。
@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
- 只有实现才知道他们是否需要确保集中访问的可变状态。
-
将实现绑定到接口时,不应该有任何作用域注释。
@Module abstract class ToastModule { // BAD, remove @Singleton @Binds @Singleton abstract ToastFactory toastFactory(RealToastFactory factory); }
启用error-prone
几个Square团队正在使用它将常见的Dagger错误检查出来。
结论
这些指导原则适用于我们:小型异构团队在大型共享Android代码库上工作。由于您的背景可能不同,因此您应该应用对您的团队最有意义的内容。
轮到你了!您遵循哪些良好做法来保持Dagger代码的清晰度?