ダガー2。パート2。 カスタムスコープ、コンポーネントの依存関係、サブコンポーネント

みなさんこんにちは!
Dagger 2に関する一連の記事を続けます。まだ最初のパートを読んでいない場合は、すぐに読んでください:)
最初の部分についてのフィードバックとコメントをありがとう。
この記事では、カスタムスコープ、コンポーネントの依存関係とサブコンポーネントを介したコンポーネントのバインドについて説明します。 また、モバイルアプリケーションのアーキテクチャなどの重要な問題、およびDagger 2がより正確でモジュールに依存しないアーキテクチャの構築にどのように役立つかについても触れます。
興味のある方はカットをお願いします!


アーキテクチャとカスタムスコープ


アーキテクチャから始めましょう。 最近、この問題に多くの注意が払われ、多くの記事とスピーチが捧げられています。 もちろん、質問は重要です。なぜなら、私たちがボートと呼ぶものから、それは浮かぶからです。 したがって、まずこれらの記事を読むことを強くお勧めします。


  1. ボブおじさんのきれいな建築
  2. Androidのクリーンアーキテクチャ
  3. ロシア語翻訳

私が本当に好きなアーキテクチャを構築するためのクリーンアーキテクチャアプローチ。 これにより、すべてのモジュールの垂直および水平構造を明確に作成できます。各クラスでは、必要なことだけを行います。 たとえば、フラグメントはUIの表示のみを担当し、ネットワーク、データベースのクエリ、ビジネスロジックの実装などを担当しません。そのため、フラグメントは紛らわしい巨大なコードになります。 これは多くの人によく知られていると思います..


例を考えてみましょう。 アプリケーションがあります。 アプリケーションにはいくつかのモジュールがあり、そのうちの1つはチャットモジュールです。 チャットモジュールには、シングルチャット、グループチャット、および設定画面の3つの画面が含まれます。
Cleanアーキテクチャを思い出して、3つの水平レベルを区別します。


  1. アプリケーション全体のレベル。 以下に、アプリケーションのライフサイクル全体で必要なオブジェクト、つまり「グローバルシングルトーン」を示します。 それらをオブジェクトにします: Context (グローバルコンテキスト)、 RxUtilsAbs (ユーティリティクラス)、 NetworkUtils (ユーティリティクラス)、およびIDataRepository (サーバーリクエストを処理するクラス)。
  2. チャットレベル。 3つのチャット画面すべてに必要なオブジェクト: IChatInteractor (特定のChatビジネスケースを実装するクラス)およびIChatStateController (チャットステータスをIChatStateControllerするクラス)。
  3. 各チャット画面のレベル。 各画面には独自のプレゼンターがあり、向きを変えることはできません。つまり、そのライフサイクルはフラグメント/アクティビティのライフサイクルとは異なります。

概略的には、ライフサイクルは次のようになります。
画像


前回の記事で「ローカル」シングルトーンについて言及したことを覚えていますか? したがって、チャットレベルと各チャット画面のオブジェクトは「ローカルシングルトーン」です。つまり、ライフサイクルが標準アクティビティ/フラグメントのライフサイクルよりも長いが、アプリケーション全体のライフサイクルよりも短いオブジェクトです。
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つのスコープアノテーションが必要です。


  1. @Singletonグローバルな@Singleton用。
  2. @ChatScopeチャットオブジェクト用。
  3. @ChatScreenScope特定のチャット画面のオブジェクト用。

@ChatScopeオブジェクトには@ChatScopeオブジェクトへのアクセス権が必要であり、 @ChatScreenScope@Singletonおよび@ChatScopeオブジェクトへのアクセス権が必要であることに@Singletonして@ChatScope
概略的に:
画像
次に、対応するDaggerコンポーネントを作成すると、それ自体が示唆されます。


  1. 「グローバルAppComponent 」を提供するAppComponent
  2. すべてのチャット画面に「ローカルChatComponent 」を提供するChatComponent
  3. 特定のチャット画面( SingleChatFragment 、つまりシングルチャット画面)に「ローカルSCComponent 」を提供するSCComponent

そして再び上記を視覚化します:
画像
その結果、3つの異なるスコープアノテーションを持つ3つのコンポーネントが取得され、それらがチェーンでリンクされます。 ChatComponentChatComponent依存し、 SCComponentChatComponent


しかし今、問題は、これらのコンポーネントをどのように適切に接続するかです。 2つの方法があります。


コンポーネントの依存関係


この通信方法は、Dagger 1から汲み上げられました。
コンポーネントの依存関係の機能にすぐに注目します。


  1. 2つの依存コンポーネントのスコープを同じにすることはできません。 こちらも似ています
  2. インターフェイスの親コンポーネントは、依存コンポーネントが使用できるオブジェクトを明示的に指定する必要があります。
  3. コンポーネントは複数のコンポーネントに依存する場合があります。

この例では、コンポーネントの依存関係を含む依存関係図は次のようになります。
画像
次に、各コンポーネントとそのモジュールを個別に検討します。


担当者
画像
コンポーネントインターフェイスでは、子コンポーネントで使用できるオブジェクトを明示的に設定します( ただし、子コンポーネントの娘ではなく、この状況については後ほど説明します)。 たとえば、子コンポーネントがNetworkUtils必要とする場合、Daggerは対応するエラーをNetworkUtilsます。
インターフェイスでは、注入の目標を設定することもできます。 つまり、コンポーネントに子コンポーネントがある場合、その依存関係を必要なクラス(アクティビティ/フラグメント/その他)に注入できないと誤解されるべきではありません。


ChatComponent
画像
ChatComponentアノテーションでは、 ChatComponentが依存するコンポーネントを明示的に指定します( ChatComponent依存ChatComponentます)。 はい、前述のように、コンポーネントには複数の親を含めることができます(新しい親コンポーネントを注釈に追加するだけです)。 ただし、コンポーネントのスコープアノテーションは異なる必要があります。 また、インターフェイスでは、子コンポーネントがアクセスできるオブジェクトを明示的に規定します。
緑の矢印に気づきましたか? 既に述べたように、 AppComponentは明示的に指定したAppComponentの依存関係を使用できます。 ただし、ここでは、実際にContextに対して行ったChatComponentでこれらの依存関係を明示的に記述しない限り、 ChatComponent子コンポーネントChatComponent AppComponentを使用できなくなります。


SCComponent
画像
SCComponentSCComponent依存しており、 SCComponent依存関係をSingleChatFragmentSingleChatFragment 。 同時に、 SingleChatFragmentこのコンポーネントは、 SCPresenterと、対応するインターフェースに明示的に登録されている親コンポーネントの他のオブジェクトの両方を注入できます。


最後のステップが残った。 これはコンポーネントを初期化することです:
画像


通常のコンポーネントと比較して、DaggerChatComponentおよびDaggerSCComponentで依存コンポーネントを初期化すると、別のメソッドが表示されますappComponent(...)DaggerChatComponent )およびchatComponent(...)DaggerSCComponent )。初期化された親コンポーネントを指定します。
ところで、コンポーネントに2つの親がある場合、2つの対応するメソッドがビルダーに表示されます。 3つの親がある場合、3つの方法などがあります。
私たちが持っているすべてのコンポーネントには、アクティビティ/フラグメントのライフサイクルとは異なる独自のライフサイクルがあるため、コンポーネントインスタンスを初期化してアプリケーションファイルに保存します。 アプリケーションクラスの例については、最後に説明します。


サブコンポーネント


この機能はすでにDagger2です。
機能:


  1. 親インターフェースで、サブコンポーネント(簡略名サブコンポーネント)を取得する方法を指定する必要があります
  2. 親のすべてのオブジェクトにサブコンポーネントからアクセスできます
  3. 親は1つしか存在できません

はい、サブコンポーネントにはコンポーネントの依存関係といくつかの違いがあります。 図とコードを検討して、違いをよりよく理解してください。


画像


この図によれば、子コンポーネントについては、親のすべてのオブジェクトが使用可能であり、コンポーネントの依存関係ツリー全体で同様に使用できることがわかります。 たとえば、 SCComponentSCComponentで使用できます。


担当者


画像


次の違いはサブコンポーネントです。 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()メソッドを使用して、対応するコンポーネントの寿命をグラフで終わらせることができます。 はい、通常のリンクのゼロ化。 ChatComponentSCComponent再び必要な場合は、新しいインスタンスを作成するplusChatComponent()およびplusSCComponent呼び出すだけです。
念のため、この例ではSCComponentが初期化されていない場合はSCComponent初期化できないことを明確にし、 NullPointerExceptionをキャッチします。
また、多くのコンポーネントとサブコンポーネントがある場合は、このコードをすべてMyAppから特別なシングルトン(たとえば、 Injector )にInjector 、必要なDaggerコンポーネントを作成、破棄、提供する責任があることに注意してください。


以上です。 ご覧のとおり、カスタムスコープ、コンポーネントの依存関係、サブコンポーネントはDagger 2の重要な要素であり、開発者はこれを使用して、より構造化された適切なアーキテクチャを作成できます。
読むことに加えて、次の記事をお勧めします。


  1. Dagger 2に関する一般的な非常に良い記事
  2. 同じ著者のカスタムスコープについて
  3. コンポーネントの依存関係とサブコンポーネントの違い

コメント、コメント、質問、いいね!
次の記事では、テストでのDagger 2の使用、およびライブラリの追加の、しかしそれほど重要ではない機能的な機能について検討します。



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


All Articles