让你的Daggers保持锋利⚔️(译)

原文链接:Keeping the Daggers Sharp ⚔️

原文作者:Py ⚔

译文出自:Dimon’s Program Basement

译者:Dimon

Dagger2是一个非常好的依赖注入库,但是其锋利的边缘处理起来也是比较棘手的。这就让我们来看看Square公司通过遵循哪些最佳事件来防止工程师们伤害自己

Dagger

与直接注入成员变量相比推荐通过构造函数注入

  • 直接注入成员变量要求为非final字段且非private字段。

    1
    2
    3
    4
    5
    // BAD
    class CardConverter {
    @Inject PublicKeyManager publicKeyManager;
    @Inject public CardConverter() {}
    }
  • 忘记加上@Inject会导致NullPointerException

    1
    2
    3
    4
    5
    6
    // BAD
    class CardConverter {
    @Inject PublicKeyManager publicKeyManager;
    Analytics analytics; // Oops, forgot to @Inject
    @Inject public CardConverter() {}
    }
  • 构造函数注入是更好的方式,因为它允许不可变并且保证线程安全的对象没有部分构造的状态。

    1
    2
    3
    4
    5
    6
    7
    // GOOD
    class CardConverter {
    private final PublicKeyManager publicKeyManager;
    @Inject public CardConverter(PublicKeyManager publicKeyManager) {
    this.publicKeyManager = publicKeyManager;
    }
    }
  • Kotlin消除了构造函数的注入样板:

    1
    2
    3
    4
    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
    // GOOD
    @Singleton
    public class BadgeCounter {
    public final Observable<Integer> badgeCount;
    @Inject public BadgeCounter(...) {
    badgeCount = ...
    }
    }
  • 如果一个对象没有可变状态时,则不需要是单例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 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方法不应该复制构造函数样板。
  • 当所有耦合问题都集中在一个地方的时候,代码更具可读性。

    1
    2
    3
    4
    5
    6
    7
    @Module
    class ToastModule {
    // BAD, remove this binding and add @Inject to RealToastFactory
    @Provides 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.
    @Singleton
    public class BadgeCounter {
    @Inject public BadgeCounter(...) {}
    }

推荐static修饰@Provides方法

  • Dagger@Provides方法能够statis修饰。

    1
    2
    3
    4
    5
    6
    7
    @Module
    class ToastModule {
    @Provides
    static ToastFactory toastFactory(RealToastFactory factory) {
    return factory;
    }
    }
  • 生成的代码可以直接调用该方法,而不必创建Modle实例。该方法调用可以由编译器内联。

    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 {
    // BAD, remove @Singleton
    @Binds @Singleton
    abstract ToastFactory toastFactory(RealToastFactory factory);
    }

启用error-prone

几个Square团队正在使用它将常见的Dagger错误检查出来。

结论

这些指导原则适用于我们:小型异构团队在大型共享Android代码库上工作。由于您的背景可能不同,因此您应该应用对您的团队最有意义的内容。

轮到你了!您遵循哪些良好做法来保持Dagger代码的清晰度?