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