web-dev-qa-db-ja.com

コンストラクターへの依存性注入にDaggerを使用する

そのため、現在Android使用するアプリを再設計しています Dagger 。私のアプリは大きくて複雑で、最近次のシナリオに遭遇しました。

オブジェクトAには、インジェクションの完全な候補である特別なDebugLoggerインスタンスが必要です。ロガーを渡す代わりに、Aのコンストラクターを介してロガーを注入できます。これは次のようになります。

_class A
{
    private DebugLogger logger;

    @Inject
    public A(DebugLogger logger)
    {
        this.logger = logger;
    }

    // Additional methods of A follow, etc.
}
_

これまでのところ、これは理にかなっています。ただし、Aは別のクラスBによって構築される必要があります。Aの複数のインスタンスを構築する必要があるため、Daggerのやり方に従って、単純に_Provider<A>_をBに挿入します。

_class B
{
    private Provider<A> aFactory;

    @Inject
    public B(Provider<A> aFactory)
    {
        this.aFactory = aFactory;
    }
}
_

わかりました、これまでのところ良いです。しかし、待ってください。突然、Aは、その構築に不可欠な「量」と呼ばれる整数などの追加の入力を必要とします。ここで、Aのコンストラクターは次のようになります。

_@Inject
public A(DebugLogger logger, int amount)
{
...
}
_

突然、この新しいパラメータが注入を妨害します。さらに、これが機能したとしても、私が間違っていない限り、プロバイダーから新しいインスタンスを取得するときに「金額」を渡す方法はありません。ここでできることはいくつかありますが、私の質問はどれが一番良いかということです。

コンストラクターの後に呼び出されることが期待されるsetAmount()メソッドを追加することで、Aをリファクタリングできます。ただし、これは醜いです。「amount」が入力されるまでAの構築を遅らせる必要があるためです。「amount」と「frequency」の2つのパラメータがある場合、2つのセッターがあります。両方のセッターが呼び出された後にAの構築が再開することを確認するための複雑なチェック、または次のように、さらに3番目のメソッドをミックスに追加する必要があります。

_(Somewhere in B):

A inst = aFactory.get();
inst.setAmount(5);
inst.setFrequency(7);
inst.doConstructionThatRequiresAmountAndFrequency();
_

もう1つの方法は、コンストラクターベースのインジェクションを使用せず、フィールドベースのインジェクションを使用することです。しかし今、私は自分の分野を公開しなければなりません。今では自分のクラスの内部データを他のクラスに公開する義務があるため、これは私にはうまくいきません。

これまでのところ、私が考えることができる唯一のやや洗練された解決策は、次のようにプロバイダーにフィールドベースのインジェクションを使用することです。

_class A
{
    @Inject
    public Provider<DebugLogger> loggerProvider;
    private DebugLogger logger;

    public A(int amount, int frequency)
    {
        logger = loggerProvider.get();
        // Do fancy things with amount and frequency here
        ...
    }
}
_

それでも、コンストラクターが呼び出される前にDaggerがプロバイダーを注入するかどうかわからないため、タイミングについてはわかりません。

もっと良い方法はありますか?ダガーがどのように機能するかについて何かが足りないだけですか?

28
Alex

あなたが話していることはアシストインジェクションとして知られており、現在ダガーは自動的にサポートしていません。

ファクトリパターンでこれを回避できます。

class AFactory {
  @Inject DebugLogger debuggLogger;

  public A create(int amount, int frequency) {
    return new A(debuggLogger, amount);
  }
}

これで、このファクトリを挿入し、それを使用してAのインスタンスを作成できます。

class B {
  @Inject AFactory aFactory;

  //...
}

また、「amount」と「frequency」を使用してAを作成する必要がある場合は、ファクトリを使用します。

A a = aFactory.create(amount, frequency);

これにより、Aは、ロガーインスタンスを提供するためにインジェクションを使用しながら、ロガー、金額、および頻度フィールドのfinalインスタンスを持つことができます。

Guiceには、これらのファクトリの作成を基本的に自動化するアシストインジェクションプラグインがあります。そこに 議論されています それらを追加する適切な方法についてDaggerメーリングリストにありますが、この記事の執筆時点では何も決定されていません。

53
Jake Wharton

コンストラクターで注射剤と非注射剤を混合しているため、問題が発生しています。大量の心痛を軽減し、コードをクリーンに保つためのインジェクションの一般的なルールは次のとおりです。

  1. Injectablesは、コンストラクターで他のinjectablesを要求できますが、newablesは要求できません。

  2. Newablesは、コンストラクターで他のnewablesを要求できますが、injectablesは要求できません。

インジェクタブルはサービスタイプのオブジェクトです。つまり、CreditCardProcessor、MusicPlayerなどの機能するオブジェクトです。

Newablesは、CreditCard、Songなどの値型オブジェクトです。

3

ジェイクの投稿が言っていることは完全に真実です。とは言うものの、私たち(GuiceとDaggerを使用するGoogleの人々の一部)は、GuiceまたはDaggerまたはスタンドアロンで使用できる「アシストインジェクション」または自動ファクトリ生成の代替バージョンに取り組んでいます。ファクトリクラスのソースコードを生成します。これらのファクトリクラスは、(適切な場合)標準のJSR-330クラスと同じように注入可能です。しかし、まだリリースされていません。

このような解決策が出るまで、ジェイク・ウォートンのアプローチをお勧めします。

3

ジェイクの投稿は素晴らしいですが、もっと簡単な方法があります。 Googleが作成した AutoFactory コンパイル時にファクトリを自動的に作成するためのライブラリ。

まず、引数を挿入するための@AutoFactoryアノテーションと@Providedアノテーションを使用してクラスAを作成します。

@AutoFactory
public class A {

    private DebugLogger logger;

    public A(@Provided DebugLogger logger, int amount, int frequency) {
        this.logger = logger;
    }
}

次に、ライブラリはコンパイル時にAFactoryクラスを作成します。したがって、ファクトリをBクラスのコンストラクタに注入するだけで済みます。

public class B {

    private final AFactory aFactory;

    @Inject
    public B(AFactory aFactory) {
        this.aFactory = aFactory;
    }

    public A createA(int amount, int frequency) {
        return aFactory.create(amount, frequency);
    }
}
2

この質問が投稿されてから数年が経過したことを付け加えたいと思います。今では、まったく同じ問題を解決し、Daggerと完全に互換性がある、JakeとSquareの友人によって作成されたAssistedInjectというライブラリがあります。 2.2。

ここで見つけることができます: https://github.com/square/AssistedInject

0
MatPag