bouzuya.hatenablog.com

ぼうずやのにっき

Dagger 2 でも javax.inject の Provider<T> が使える

Dagger 2 での Provider<T> について書く。

Dagger 2 の過去の記事は↓。

Provider<T>JSR 330javax.inject に含まれている interface だ。2017-06-19 で内部的に生成されていると書いたものの共通の interface だ。

interface Provider<T> {
  T get();
}

この Provider<T> に対しての注入は T への提供がされていれば、問題なくできる。これは↓のようなことだ。

class A {
  private final B b;
  @Inject A(Provider<B> bProvider) {
    this.b = bProvider.get();
  }
}

class B {}

class XModule {
  @Provides B provideB() { return new B(); }
  // @Provides Provider<B> providerBProvider() { ... } のようなものは不要
}

/* ... Component は省略 ... */

で、この Provider<T> に何ができるかは Provider<T> のドキュメントにある。

Provides instances of T. Typically implemented by an injector. For any type T that can be injected, you can also inject Provider. Compared to injecting T directly, injecting Provider enables:

retrieving multiple instances.
lazy or optional retrieval of an instance.
breaking circular dependencies.
abstracting scope so you can look up an instance in a smaller scope from an instance in a containing scope.

For example:

class Car { @Inject Car(Provider seatProvider) { Seat driver = seatProvider.get(); Seat passenger = seatProvider.get(); ... } }

2017-06-22 で循環依存について書いた。これの回避策はいろいろあるが、 Provider<T> を使うのもひとつだ。

class A {
  private final B;
  @Inject A(B b) {
    this.b = b;
  }
}

class B {
  private final C;
  @Inject B(C c) {
    this.c = c;
  }
}

class C {
  private final A;
  @Inject C(Provider<A> aProvider) { // Provider<T>
    this.a = aProvider.get();
  }
}

話はすこし変わる。ぼくが探していたものとしては、コンストラクタに依存以外の引数がある場合の対処法だ。

不変クラスが望ましいのは説明不要だとして、不変クラスのためにはコンストラクタインジェクションが望ましい。 Java ではフィールドの final 化にはコンストラクタでの設定が不可欠だからだ。 (もちろん、フィールドを final にしなくても不変クラスは実現できるのだけど、より意図が伝わりやすいだろう) 。同時に、コンストラクタインジェクションを採用すると、コンストラクタの呼び出しが DI コンテナの管理下になるため、依存以外の引数を取るのが難しくなる。

この問題を説明すると↓のようになる。

class A {
  private final B b; // final のためにフィールドインジェクションは避けたい
  private final C c;
  A(B b, C c) { // B を @Inject したいが、 C を動的に変更したいため、できない。
    this.b = b;
    this.c = c;
  }
}

おそらく一般的な解決策はファクトリーを使って回避する方法だ。ファクトリーに依存させ、ファクトリーのメソッドに依存以外の引数を取れば良い。ファクトリーに依存させるところまでは、上記の Provider<T> がまさにそれだ。

class A {
  private final B b;
  private final C c;
  A(B b, C c) {
    this.b = b;
    this.c = c;
  }
}
class AFactory {
  private final B b;
  @Inject AFactory(B b) { // A ではなく A の Factory を Inject できるようにする
    this.b = b;
  }
  A create(C c) {
    return new A(this.b, c);
  }
}

Guice では AssistedInject というものがあるらしい。ぼくの要求どおりなので、まさにこれがほしいんだけど、 Dagger 2 にはおそらくない。ここは諦めてファクトリーを使っていくつもりだ。

ただ、ここでもせっかくなので Provider<T> を活かしていくと良い。上記の AssistedInject のドキュメントにもあるが、上記のファクトリーの例は B に直接依存させるより createget するほうが望ましい。

class AFactory {
  private final Provider<B> bProvider;
  @Inject AFactory(Provider<B> bProvider) { // B よりも Provider<B>
    this.bProvider = bProvider;
  }
  A create(C c) {
    return new A(this.bProvider.get(), c);
  }
}

細かい点ではあるが、こういうものの積み重ねが意外と大きい差になるはずだ。