みなさんこんにちは!
Dagger 2に関する一連の記事を続けます。まだ最初のパートを読んでいない場合は、すぐに読んでください:)
最初の部分についてのフィードバックとコメントをありがとう。
この記事では、カスタムスコープ、コンポーネントの依存関係とサブコンポーネントを介したコンポーネントのバインドについて説明します。 また、モバイルアプリケーションのアーキテクチャなどの重要な問題、およびDagger 2がより正確でモジュールに依存しないアーキテクチャの構築にどのように役立つかについても触れます。
興味のある方はカットをお願いします!
アーキテクチャとカスタムスコープ
アーキテクチャから始めましょう。 最近、この問題に多くの注意が払われ、多くの記事とスピーチが捧げられています。 もちろん、質問は重要です。なぜなら、私たちがボートと呼ぶものから、それは浮かぶからです。 したがって、まずこれらの記事を読むことを強くお勧めします。
私が本当に好きなアーキテクチャを構築するためのクリーンアーキテクチャアプローチ。 これにより、すべてのモジュールの垂直および水平構造を明確に作成できます。各クラスでは、必要なことだけを行います。 たとえば、フラグメントはUIの表示のみを担当し、ネットワーク、データベースのクエリ、ビジネスロジックの実装などを担当しません。そのため、フラグメントは紛らわしい巨大なコードになります。 これは多くの人によく知られていると思います..
例を考えてみましょう。 アプリケーションがあります。 アプリケーションにはいくつかのモジュールがあり、そのうちの1つはチャットモジュールです。 チャットモジュールには、シングルチャット、グループチャット、および設定画面の3つの画面が含まれます。
Cleanアーキテクチャを思い出して、3つの水平レベルを区別します。
- アプリケーション全体のレベル。 以下に、アプリケーションのライフサイクル全体で必要なオブジェクト、つまり「グローバルシングルトーン」を示します。 それらをオブジェクトにします:
Context
(グローバルコンテキスト)、RxUtilsAbs
(ユーティリティクラス)、NetworkUtils
(ユーティリティクラス)、およびIDataRepository
(サーバーリクエストを処理するクラス)。 - チャットレベル。 3つのチャット画面すべてに必要なオブジェクト:
IChatInteractor
(特定のChatビジネスケースを実装するクラス)およびIChatStateController
(チャットステータスをIChatStateController
するクラス)。 - 各チャット画面のレベル。 各画面には独自のプレゼンターがあり、向きを変えることはできません。つまり、そのライフサイクルはフラグメント/アクティビティのライフサイクルとは異なります。
概略的には、ライフサイクルは次のようになります。
前回の記事で「ローカル」シングルトーンについて言及したことを覚えていますか? したがって、チャットレベルと各チャット画面のオブジェクトは「ローカルシングルトーン」です。つまり、ライフサイクルが標準アクティビティ/フラグメントのライフサイクルよりも長いが、アプリケーション全体のライフサイクルよりも短いオブジェクトです。
Dagger 2が登場しました。これには、すばらしいスコープメカニズムがあります。 このメカニズムは、対応するスコープが存在する限り、必要なクラスの単一のインスタンスを作成して保存します。 「既存のスコープが存在する限り」というフレーズはやや混乱を招き、疑問を投げかけます。 恐れてはいけません、すべてが以下で明らかになります。
前の記事では、「グローバルシングルトン」スコープ@Singleton
タグを付け@Singleton
。 このスコープは、アプリケーションの存続期間を通じて存在していました。 ただし、独自のカスタムスコープアノテーションを作成することもできます。 例:
@Scope @Retention(RetentionPolicy.RUNTIME) public @interface ChatScope { }
そして、Dagger 2で@Singleton
アノテーションを作成すると次のようになります。
@Scope @Retention(RetentionPolicy.RUNTIME) public @interface Singleton { }
つまり、 @Singleton
@ChatScope
も同じであり、デフォルトで@Singleton
アノテーションのみ@Singleton
ライブラリによって提供されます。 そして、これらの注釈の目的は1つです。「スコープ」オブジェクトと「スコープ外」オブジェクトのどちらを提供するかをDaggerに伝えることです。 しかし、繰り返しますが、私たちはオブジェクトの「スコープ」のライフサイクルに責任があります。
例に戻ります。 現在のアーキテクチャによれば、独自の「寿命」を持つオブジェクトの3つのグループを取得します。 したがって、3つのスコープアノテーションが必要です。
-
@Singleton
グローバルな@Singleton
用。 -
@ChatScope
チャットオブジェクト用。 -
@ChatScreenScope
特定のチャット画面のオブジェクト用。
@ChatScope
オブジェクトには@ChatScope
オブジェクトへのアクセス権が必要であり、 @ChatScreenScope
は@Singleton
および@ChatScope
オブジェクトへのアクセス権が必要であることに@Singleton
して@ChatScope
。
概略的に:
次に、対応するDaggerコンポーネントを作成すると、それ自体が示唆されます。
- 「グローバル
AppComponent
」を提供するAppComponent
。 - すべてのチャット画面に「ローカル
ChatComponent
」を提供するChatComponent
。 - 特定のチャット画面(
SingleChatFragment
、つまりシングルチャット画面)に「ローカルSCComponent
」を提供するSCComponent
。
そして再び上記を視覚化します:
その結果、3つの異なるスコープアノテーションを持つ3つのコンポーネントが取得され、それらがチェーンでリンクされます。 ChatComponent
はChatComponent
依存し、 SCComponent
しChatComponent
。
しかし今、問題は、これらのコンポーネントをどのように適切に接続するかです。 2つの方法があります。
コンポーネントの依存関係
この通信方法は、Dagger 1から汲み上げられました。
コンポーネントの依存関係の機能にすぐに注目します。
- 2つの依存コンポーネントのスコープを同じにすることはできません。 こちらも似ています 。
- インターフェイスの親コンポーネントは、依存コンポーネントが使用できるオブジェクトを明示的に指定する必要があります。
- コンポーネントは複数のコンポーネントに依存する場合があります。
この例では、コンポーネントの依存関係を含む依存関係図は次のようになります。
次に、各コンポーネントとそのモジュールを個別に検討します。
担当者
コンポーネントインターフェイスでは、子コンポーネントで使用できるオブジェクトを明示的に設定します( ただし、子コンポーネントの娘ではなく、この状況については後ほど説明します)。 たとえば、子コンポーネントがNetworkUtils
必要とする場合、Daggerは対応するエラーをNetworkUtils
ます。
インターフェイスでは、注入の目標を設定することもできます。 つまり、コンポーネントに子コンポーネントがある場合、その依存関係を必要なクラス(アクティビティ/フラグメント/その他)に注入できないと誤解されるべきではありません。
ChatComponent
ChatComponent
アノテーションでは、 ChatComponent
が依存するコンポーネントを明示的に指定します( ChatComponent
依存ChatComponent
ます)。 はい、前述のように、コンポーネントには複数の親を含めることができます(新しい親コンポーネントを注釈に追加するだけです)。 ただし、コンポーネントのスコープアノテーションは異なる必要があります。 また、インターフェイスでは、子コンポーネントがアクセスできるオブジェクトを明示的に規定します。
緑の矢印に気づきましたか? 既に述べたように、 AppComponent
は明示的に指定したAppComponent
の依存関係を使用できます。 ただし、ここでは、実際にContext
に対して行ったChatComponent
でこれらの依存関係を明示的に記述しない限り、 ChatComponent
子コンポーネントChatComponent
AppComponent
を使用できなくなります。
SCComponent
SCComponent
はSCComponent
依存しており、 SCComponent
依存関係をSingleChatFragment
しSingleChatFragment
。 同時に、 SingleChatFragment
このコンポーネントは、 SCPresenter
と、対応するインターフェースに明示的に登録されている親コンポーネントの他のオブジェクトの両方を注入できます。
最後のステップが残った。 これはコンポーネントを初期化することです:
通常のコンポーネントと比較して、DaggerChatComponentおよびDaggerSCComponent
で依存コンポーネントを初期化すると、別のメソッドが表示されますappComponent(...)
( DaggerChatComponent
)およびchatComponent(...)
( DaggerSCComponent
)。初期化された親コンポーネントを指定します。
ところで、コンポーネントに2つの親がある場合、2つの対応するメソッドがビルダーに表示されます。 3つの親がある場合、3つの方法などがあります。
私たちが持っているすべてのコンポーネントには、アクティビティ/フラグメントのライフサイクルとは異なる独自のライフサイクルがあるため、コンポーネントインスタンスを初期化してアプリケーションファイルに保存します。 アプリケーションクラスの例については、最後に説明します。
サブコンポーネント
この機能はすでにDagger2です。
機能:
- 親インターフェースで、サブコンポーネント(簡略名サブコンポーネント)を取得する方法を指定する必要があります
- 親のすべてのオブジェクトにサブコンポーネントからアクセスできます
- 親は1つしか存在できません
はい、サブコンポーネントにはコンポーネントの依存関係といくつかの違いがあります。 図とコードを検討して、違いをよりよく理解してください。
この図によれば、子コンポーネントについては、親のすべてのオブジェクトが使用可能であり、コンポーネントの依存関係ツリー全体で同様に使用できることがわかります。 たとえば、 SCComponent
はSCComponent
で使用できます。
担当者
次の違いはサブコンポーネントです。 AppComponent
インターフェースでAppComponent
、後続のChatComponent
初期化のためのメソッドAppComponent
作成します。 繰り返しますが、このメソッドの主なものは戻り値( ChatComponent
)と引数( ChatModule
)です。
ChatModule
(既定のコンストラクター)に何も渡す必要がないので、 plusChatComponent
メソッドでこの引数を省略することもできます。 ただし、依存関係をより明確に把握し、教育目的で使用するために、すべてを可能な限り詳細に残しましょう。
ChatComponent
ChatComponent
子コンポーネントと親コンポーネントの両方です。 親であるという事実は、インターフェイスでSCComponent
を作成する方法を示しています。 そして、コンポーネントが子であるという事実は、 @Subcomponent
アノテーションによって示されます。
SCComponent
前述したように、アクティビティ/フラグメントのライフサイクルとは異なる独自のライフサイクルを持っているすべてのコンポーネントがあるため、コンポーネントインスタンスを初期化してアプリケーションファイルに格納します。
public class MyApp extends Application { protected static MyApp instance; public static MyApp get() { return instance; } // Dagger 2 components private AppComponent appComponent; private ChatComponent chatComponent; private SCComponent scComponent; @Override public void onCreate() { super.onCreate(); instance = this; // init AppComponent on start of the Application appComponent = DaggerAppComponent.builder() .appModule(new AppModule(instance)) .build(); } public ChatComponent plusChatComponent() { // always get only one instance if (chatComponent == null) { // start lifecycle of chatComponent chatComponent = appComponent.plusChatComponent(new ChatModule()); } return chatComponent; } public void clearChatComponent() { // end lifecycle of chatComponent chatComponent = null; } public SCComponent plusSCComponent() { // always get only one instance if (scComponent == null) { // start lifecycle of scComponent scComponent = chatComponent.plusSComponent(new SCModule()); } return scComponent; } public void clearSCComponent() { // end lifecycle of scComponent scComponent = null; } }
そして、コードのコンポーネントライフサイクルをようやく確認できます。 AppComponent
についてのすべては明確であり、アプリケーションの開始時に初期化され、それ以上触れません。 ただし、 plusChatComponent()
およびplusSCComponent
を使用して、必要に応じてChatComponent
およびSCComponent
を初期化しplusSCComponent
。 これらのメソッドは、コンポーネントの単一インスタンスを返す役割も果たします。
たとえば、もう一度電話をかけると、
scComponent = chatComponent.plusSComponent(new SCModule());
SCComponent
新しいインスタンスは、依存関係グラフでSCComponent
ます。
clearChatComponent()
およびclearSCComponent()
メソッドを使用して、対応するコンポーネントの寿命をグラフで終わらせることができます。 はい、通常のリンクのゼロ化。 ChatComponent
とSCComponent
再び必要な場合は、新しいインスタンスを作成するplusChatComponent()
およびplusSCComponent
呼び出すだけです。
念のため、この例ではSCComponent
が初期化されていない場合はSCComponent
初期化できないことを明確にし、 NullPointerException
をキャッチします。
また、多くのコンポーネントとサブコンポーネントがある場合は、このコードをすべてMyApp
から特別なシングルトン(たとえば、 Injector
)にInjector
、必要なDaggerコンポーネントを作成、破棄、提供する責任があることに注意してください。
以上です。 ご覧のとおり、カスタムスコープ、コンポーネントの依存関係、サブコンポーネントはDagger 2の重要な要素であり、開発者はこれを使用して、より構造化された適切なアーキテクチャを作成できます。
読むことに加えて、次の記事をお勧めします。
コメント、コメント、質問、いいね!
次の記事では、テストでのDagger 2の使用、およびライブラリの追加の、しかしそれほど重要ではない機能的な機能について検討します。