Dagger 2マルチバインディング

この記事では、マルチバインディングを使用する機能について説明します。これは、依存関係のプロビジョニングに関連する多くの問題の解決に役立ちます。


この記事を読むには、Dagger 2の基本的な知識が必要です。例では、Daggerバージョン2.11を使用しました。


Dagger 2を使用すると、オブジェクトが異なるモジュールにバインドされている場合でも、複数のオブジェクトをコレクションにバインドできます。 Dagger 2は、 SetおよびMapマルチバインディングをサポートしています。


マルチバインディングを設定する


Setに要素を追加するには、モジュールの@Providesメソッドに@IntoSetアノテーションを追加するだけです:


 @Module public class ModuleA { @IntoSet @Provides public FileExporter xmlFileExporter(Context context) { return new XmlFileExporter(context); } } @Module public class ModuleB { @IntoSet @Provides public FileExporter provideCSVFileExporter(Context context) { return new CSVFileExporter(context); } } 

これらの2つのモジュールをコンポーネントに追加します。


 @Component(modules = {ModuleA.class, ModuleB.class}) public interface AppComponent { //inject methods } 

なぜなら セット内の要素のバインディングを含む2つのモジュールを1つのコンポーネントに結合しました。短剣はこれらの要素を1つのコレクションに結合します。


 public class Values { @Inject public Values(Set<FileExporter> values) { //  values: [XmlFileExporter, CSVFileExporter] } } 

一度に複数の要素を追加することもできます。 @Provideためには、 @ProvideメソッドがSet戻り値の型を持ち、 @Provideメソッドの上に@ElementsIntoSetアノテーションを配置する必要があります。


ModuleBを交換します。


 @Module public class ModuleB { @ElementsIntoSet @Provides public Set<FileExporter> provideFileExporters(Context context) { return new HashSet<>(Arrays.asList(new CSVFileExporter(context), new JSONFileExporter(context))); } } 

結果:


 public class Values { @Inject public Values(Set<FileExporter> values) { //  values: [XmlFileExporter, CSVExporter, JSONFileExporter] } } 

コンポーネントを介して依存関係を提供できます。


 @Component(modules = {ModuleA.class, ModuleB.class}) public interface AppComponent { Set<FileExporter> fileExporters(); } Set<FileExporter> fileExporters = DaggerAppComponent .builder() .context(this) .build() .fileExporters(); 

また、 @Providesメソッドに対して@Qualifierを使用してコレクションを提供し、それによってそれらを分離することもできます。


ModuleBを再度交換します。


 @Module public class ModuleB { @ElementsIntoSet @Provides @Named("CSV_JSON") public Set<FileExporter> provideFileExporters(Context context) { return new HashSet<>(Arrays.asList(new CSVFileExporter(context), new JSONFileExporter(context))); } } //  Qualifier public class Values { @Inject public Values(Set<FileExporter> values) { //  values: [XmlFileExporter]. //    ,  //   c ModuleA. } } //  Qualifier public class Values { @Inject public Values(@Named("CSV_JSON") Set<FileExporter> values) { //  values: [CSVExporter, JSONFileExporter] } } //  @Component(modules = {ModuleA.class, ModuleB.class}) public interface AppComponent { @Named("CSV_JSON") Set<FileExporter> fileExporters(); } 

Dagger 2は、オブジェクトの初期化を最初の呼び出しまで延期する機能を提供します。この機能はコレクション用です。 Dagger 2の兵器庫には、遅延初期化を実現する2つの方法がありProvider<T> Lazy<T>インターフェイスを使用する方法です。


遅延注入


T依存関係の場合、 Lazy<T>適用できます。このメソッドにより、 Lazy<T>.get()の最初の呼び出しまで初期化を遅らせることができます。 Tシングルトンの場合、同じインスタンスが常に返されます。 Tスコープ外の場合、依存関係TLazy<T>.get呼び出し時に作成され、 Lazy<T>.get内のキャッシュに入れられ、このLazy<T>.get()以降の各呼び出しはキャッシュされた値を返します。


例:


 @Module public class AppModule { @Singleton @Provides public GroupRepository groupRepository(Context context) { return new GroupRepositoryImpl(context); } @Provides return new UserRepositoryImpl(context); public UserRepository userRepository(Context context) { } } public class MainActivity extends AppCompatActivity { @Inject Lazy<GroupRepository> groupRepositoryInstance1; @Inject Lazy<GroupRepository> groupRepositoryInstance2; @Inject Lazy<UserRepository> userRepositoryInstance1; @Inject Lazy<UserRepository> userRepositoryInstance2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerAppComponent .builder() .context(this) .build() .inject(this); //GroupRepository @Singleton scope GroupRepository groupRepository1 = groupRepositoryInstance1.get(); GroupRepository groupRepository2 = groupRepositoryInstance1.get(); GroupRepository groupRepository3 = groupRepositoryInstance2.get(); //UserRepository unscope UserRepository userRepository1 = userRepositoryInstance1.get(); UserRepository userRepository2 = userRepositoryInstance1.get(); UserRepository userRepository3 = userRepositoryInstance2.get(); } } 

インスタンスgroupRepository1, groupRepository2およびgroupRepository3は等しいため、 スコープシングルトンがあります。


インスタンスuserRepository1userRepository2は等しくなります。 userRepositoryInstance1.get()の最初の呼び出しで、オブジェクトが作成され、 userRepositoryInstance1内にキャッシュされますが、 userRepository3は異なるインスタンスを持ちます。 彼には別のLazyあり、 get()が初めて呼び出されました。


プロバイダー注入


Provider<T>では、オブジェクトの初期化を遅らせることもできますが、 Lazy<T>とは異なり、スコープ外依存関係値はProvider<T>キャッシュされず、新しいインスタンスを返すたびにキャッシュされません。 このアプローチは、たとえば、シングルトンミサゴを備えた特定のファクトリーがあり、このファクトリーが毎回新しいオブジェクトを提供する必要がある場合に必要になることがあります。例を考えてみましょう。


 @Module public class AppModule { @Provides public Holder provideHolder() { return new Holder(); } @Provides @Singleton public HolderFactory provideHolderFactory(Provider<Holder> holder) { return new HolderFactoryImpl(holder); } } public class HolderFactoryImpl implements HolderFactory { private Provider<Holder> holder; public HolderFactoryImpl(Provider<Holder> holder) { this.holder = holder; } public Holder create() { return holder.get(); } } public class MainActivity extends AppCompatActivity { @Inject HolderFactory holderFactory; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerAppComponent .builder() .context(this) .build() .inject(this); Holder holder1 = holderFactory.create(); Holder holder2 = holderFactory.create(); } } 

ここでは、 holder1holder2に異なるインスタンスがありますholder2 Lazy<T>代わりにLazy<T>を使用する場合、これらのオブジェクトにはキャッシュのために1つのインスタンスがあります。


遅延初期化はSetも適用できます:
Lazy<Set<T>> Provider<Set<T>> は、次のように使用できません: Set<Lazy<T>>


 public class MainActivity extends AppCompatActivity { @Inject Lazy<Set<FileExporter>> fileExporters; //… // Set<FileExporter> exporters = fileExporters.get(); } 

マルチバインディングをマップする


要素をMapに追加するには、モジュールの@Providesメソッドの上に@IntoMapアノテーションとキーアノテーション( @MapKey Heirs)を追加する必要があります。


 @Module public class ModuleA { @IntoMap @Provides @StringKey("xml") public FileExporter xmlFileExporter(Context context) { return new XmlFileExporter(context); } } @Module public class ModuleB { @IntoMap @StringKey("csv") @Provides public FileExporter provideCSVFileExporter(Context context) { return new CSVFileExporter(context); } } @Component(modules = {ModuleA.class, ModuleB.class}) public interface AppComponent { //inject methods } 

結果:


 public class Values { @Inject public Values(Map<String, FileExporter> values) { //  values {xml=XmlFileExporter,csv=CSVExporter} } } 

Setと同様に、コンポーネントで2つのモジュールを指定したため、Daggerは値を1つのMap結合しました。 @Qualifier使用することもでき@Qualifier


Map標準キータイプ:



Dagger-Androidアドオンモジュールの標準キータイプ:



ActivityKey例の実装は次のようになります。


 @MapKey @Target(METHOD) public @interface ActivityKey { Class<? extends Activity> value(); } 

上記のように、またはenumを使用して、独自のキータイプを作成できます。


 public enum Exporters { XML, CSV } @MapKey @Target(METHOD) public @interface ExporterKey { Exporters value(); } @Module public class ModuleA { @IntoMap @Provides @ExporterKey(Exporters.XML) public FileExporter xmlFileExporter(Context context) { return new XmlFileExporter(context); } } @Module public class ModuleB { @IntoMap @ExporterKey(Exporters.CSV) @Provides public FileExporter provideCSVFileExporter(Context context) { return new CSVFileExporter(context); } } public class Values { @Inject public Values(Map<Exporters, FileExporter> values) { //  values {XML=XmlFileExporter,CSV=CSVExporter} } } 

Set同様に、遅延初期化を適用できます。
Lazy<Map<K,T>>, Provider<Map<K,T>>


Mapを使用すると、コレクション自体の初期化だけでなく、個々の要素の初期化を延期して、キー( Map<K,Provider<T>> )を使用して毎回新しい値を受け取ることができます。


 public class MainActivity extends AppCompatActivity { @Inject Map<Exporters, Provider<FileExporter>> exporterMap; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DaggerAppComponent .builder() .context(this) .build(); FileExporter fileExporter1 = exporterMap.get(Exporters.CSV).get(); FileExporter fileExporter2 = exporterMap.get(Exporters.CSV).get(); } } 

fileExporter1fileExporter2には異なるインスタンスがあります。 また、 Exports.XML要素は初期化さえされてExports.XML 私たちは彼に連絡しませんでした。


Map<K, Lazy<T>>は使用できません


空のコレクションを検証するには、抽象メソッドに@Multibindsアノテーションを追加する必要があります。


 @Module public abstract class AppModule { @Multibinds abstract Map<Exporters, FileExporter> exporters(); } 

これは、たとえば、このコレクションを既に使用したいが、実装されたモジュールがまだ利用可能でない(実装されていない)場合、およびモジュールを実装および追加するときに、値を共通のコレクションに結合する場合に必要になることがあります。


サブコンポーネントとマルチバインディング


親コンポーネントは、親コンポーネントのモジュールでのみ指定されたコレクションにアクセスでき、サブコンポーネントは親コンポーネントのすべてのコレクションを「継承」し、それらをサブコンポーネントコレクションと結合します。


 @Module public class AppModule { @IntoMap @Provides @ExporterKey(Exporters.XML) public FileExporter xmlFileExporter(Context context) { return new XmlFileExporter(context); } } @Module public class ActivityModule { @IntoMap @ExporterKey(Exporters.CSV) @Provides public FileExporter provideCSVFileExporter(Context context) { return new CSVFileExporter(context); } } @Singleton @Component(modules = {AppModule.class}) public interface AppComponent { ActivitySubComponent provideActivitySubComponent(); //   {xml=XmlFileExporter} Map<Exporters, FileExporter> exporters(); @Component.Builder interface Builder { @BindsInstance Builder context(Context context); AppComponent build(); } } @ActivityScope @Subcomponent(modules = {ActivityModule.class}) public interface ActivitySubComponent { //   {XML=XmlFileExporter,CSV=CSVExporter} Map<Exporters, FileExporter> exporters(); } 

@Binds +マルチバインディング


Dagger 2では、抽象@Bindsメソッドを使用してオブジェクトをコレクションにバインドできます。


 @Module public abstract class LocationTrackerModule { @Binds @IntoSet public abstract LocationTracker netLocationTracker(NetworkLocationTracker tracker); @Binds @IntoSet public abstract LocationTracker fileLocationTracker(FileLocationTracker tracker); } 

プラグイン


プラグインで設計されたアプリケーションを構築するために、依存性注入フレームワークを使用してインターフェイスを実装から分離し、「プラグイン」をさまざまなアプリケーションで再利用できるようにします。



マルチバインディングを使用して、多くのプラグインの拡張ポイントとなるインターフェイスとプロバイダーメソッドを作成できます。



おわりに


私の意見では、Multibindingsは依存関係の提供を整理するための十分な機会を提供し、工場を美しく整理することができ、拡張アーキテクチャの実装にも適しています。


GitHubの



Source: https://habr.com/ru/post/J336414/


All Articles