ダガー2のわかりやすい紹介。パート1

依存性注入とは何ですか、ダガーとは何であり、よりクリーンで簡単なテスト用のコードを書くのにどのように役立つのでしょうか。



翻訳者からの免責事項。 この翻訳は独学の目的で行われ、Habréでは、私のような、第5世代のJavaを話す開発者として生まれなかった多くの初心者のAndroid開発者は、長年にわたる階層化の概念と開発方法の最終製品を理解するのが非常に難しいという前提でレイアウトされました。 この一連の記事は、複雑なこと説明する優れた例であり、私と同じように楽しんでください。 気づいたすべてのエラーと不正確さについて、PMでお知らせください。



依存性注入(DI)は、アプリケーションをテストで簡単にカバーするための優れた手法です。Dagger2は、この目的のために設計された最も人気のあるJava / Androidフレームワークの1つです。 ただし、Dagger 2のほとんどの入門コースは、読者がDIとそのかなり複雑な用語に既に精通しているという前提に基づいているため、初心者にとってエントリーは困難です。



このシリーズの記事では、Dagger 2のよりわかりやすい紹介と、すぐにコンパイルできるコードの例を多数紹介します。



ツリーに自分の考えを広めないために、DIフレームワークの話は、数え切れないほどの種類のように、意図的に残しています。 クラスをコンストラクターに導入することから始め、移動中に他のオプションを確認します。



依存性注入とは正確には何ですか?



依存性注入は、クラスのテストと再利用を簡素化する手法です。 適用方法の例を見てみましょう。 現在の気象条件をコンソールに出力するアプリケーションを作成する必要があるとしましょう。 単純な実装は次のようになります。



public class WeatherReporter { private final WeatherService weatherService; private final LocationManager locationManager; public WeatherReporter() { weatherService = new WeatherService(); locationManager = new LocationManager(); } public void report() { // locationManager.getCurrentLocation() // weatherService.getTemperature(location) // print(temperature) } }
      
      





一部のメソッドは、この場合重要ではないため、意図的に省略されています。 WeatherReporterは、ジョブを実行するために2つのオブジェクトを必要とすることに注意してください。ユーザーの位置を決定するLocationManagerと、指定された座標の温度を表示するWeatherServiceです。



適切に設計されたオブジェクト指向アプリケーションでは、各オブジェクトにはわずかな責任しか含まれておらず、残りの作業は他のオブジェクトに委任されます。 これらの他のオブジェクトは依存関係と呼ばれます。 オブジェクトが実際の作業を開始する前に、その依存関係をすべて何らかの方法で解決する必要があります。 たとえば、 WeatherReporterの場合、これらのオブジェクトの新しいインスタンスをコンストラクターで作成することにより依存関係が解決されます。



小規模なアプリケーションの場合、クラスコンストラクターで依存関係を初期化することは非常に効果的ですが、アプリケーションが成長するにつれて、このアプローチには多くの欠点が明らかになります。 まず、クラスの柔軟性が低下します。 たとえば、アプリケーションをマルチプラットフォームにする必要がある場合、現在のLocationManagerを別のものに置き換える必要があるかもしれませんが、それほど単純ではありません。 複数の場所で同じLocationManagerを使用したい場合がありますが、クラスを変更するまでこれは困難です。



第二に、私たちのクラスは隔離されたテストに適していません。 1つのWeatherReporterオブジェクトを作成するには、他の2つのオブジェクトを作成する必要があります。これらのオブジェクトは、元のオブジェクトとともに最終的にテスト対象となります。 これは、依存関係の1つが高価な外部リソース(インターネット接続など)に依存している場合、またはそれ自体に多数の依存関係がある場合、深刻な問題になる可能性があります。



この場合の悪の根源は、クラスに2つの異なる責任があることです。 彼は自分のタスクを完了する方法だけでなく、その実装に必要なコンポーネントをどこで見つけることができるかも知っている必要があります。 代わりに、作業を実行するために必要なすべてのものをオブジェクトに提供すると、示された問題はなくなります。 また、テストの簡素化は言うまでもなく、クラスとアプリケーションの他のコンポーネントとの対話を容易にします。



 public class WeatherReporter { private final WeatherService weatherService; private final LocationManager locationManager; public WeatherReporter(WeatherService weatherService, LocationManager locationManager) { this.weatherService = weatherService; this.locationManager = locationManager; } public void report() { // locationManager.getCurrentLocation() // weatherService.getTemperature(location) // print(temperature) } }
      
      





このアプローチは、 依存性注入と呼ばれます。 DIに依存するアプリケーションでは、オブジェクトは依存関係を探し回ったり、自分で作成したりする必要はありません。 それらに提供される( 実装される )すべての依存関係は、すでに使用可能です。



グラフ構築



もちろん、ある時点で、誰かがすべての依存関係を初期化し、それらを必要とするオブジェクトに提供する必要があります。 依存関係グラフの構築と呼ばれるこの手順は、通常、アプリケーションへのエントリポイントで実行されます。 たとえば、デスクトップアプリケーションでは、次の例のように、このコードはmainメソッド内にあります。 Androidアプリケーションでは、これはonCreateアクティビティメソッド内で実行できます。



 public class Application { public static void main(String args[]) { WeatherService ws = new WeatherService(); LocationManager lm = new LocationManager(); WeatherReporter reporter = new WeatherReporter(ws, lm); reporter.report(); } }
      
      





前の例のようにプロジェクトが単純な場合、 mainメソッドのいくつかの依存関係の初期化と実装が正当化されます。 ただし、ほとんどのプロジェクトは、解決する必要がある依存関係を持つ多くのクラスで構成されています。 これらすべてを一緒に初期化およびリンクするには、大量のコードを記述する必要があります。 さらに悪いことに、アプリケーションに新しいクラスを追加したり、既存のクラスに新しい依存関係を追加すると、このコードは定期的に変更されます。



この問題を説明するために、例をより現実的なものに変えましょう。 実際には、 WeatherServiceクラスは、たとえば、ネットワークと通信するためにWebSocketを必要とします。 LocationManager 'uは、ハードウェアと対話するためにGPSProviderを必要とします。 とりわけ、ほとんどのクラスでは、デバッグ情報をコンソールに出力するためにLoggerが必要になります 。 変更されたmainメソッドは次のようになります。



 public class Application { public static void main(String args[]) { Logger logger = new Logger(); WebSocket socket = new WebSocket(); GPSProvider gps = new GPSProvider(); WeatherService ws = new WeatherService(logger, socket); LocationManager lm = new LocationManager(logger, gps); WeatherReporter reporter = new WeatherReporter(logger, ws, lm); reporter.report(); } }
      
      





非常に迅速に、アプリケーションへのエントリポイントは、豊富な初期化コードから膨らみ始めました。 本当に必要なWeatherReporterオブジェクトのみを作成するには、他の多くのオブジェクトを手動で初期化する必要があります。 アプリケーションが成長し、クラスになると、 メインメソッドは、ある日完全にサポートされなくなるまで膨張し続けます。



Dagger 2はどのように役立ちますか?



Dagger 2は、いくつかの注釈に基づいて、ほとんどの初期化コードを生成するオープンソースツールです。 Dagger 2を使用する場合、アプリケーションのエントリポイントは、所有するクラスの数やそれらに存在する依存関係の数に関係なく、わずか数行のコードで記述できます。 以下は、Daggerを使用した例の新しいメインメソッドです。



 public class Application { public static void main(String args[]) { AppComponent component = DaggerAppComponent.create(); WeatherReporter reporter = component.getWeatherReporter(); reporter.report(); } }
      
      





依存関係を解決したり、これらの依存関係がどのように絡み合っているかを示すためのコードを記述する必要がなくなったことに注意してください。 これはすでに行われています。 プロジェクトのコンパイル時に自動的に生成されるDaggerAppComponentクラスは、 WeatherReporterクラスにLoggerLocationManagerWeatherServiceが必要であり、 さらにGPSProviderWebSocketが必要であることを知るのに十分スマートです。 getWeatherReporterメソッドが呼び出されると、これらのオブジェクトがすべて正しい順序で作成され、それらの間にリンクが作成され、必要なものだけが返されます。



Daggerを機能させるには、いくつかの手順に従う必要があります。 まず、Daggerが認識する必要がある各クラスのコンストラクターに注釈を追加します。 特定のクラスに対してこれを行う方法の例を以下に示しますが、同様に、ある種の依存関係を必要とする、またはそれ自体が他のクラスの依存関係として機能するすべてのクラスに注釈を付ける必要があります。



 public class GPSProvider { @Inject public GPSProvider() { // ... } }
      
      





Dagger固有のコード汚染が心配な場合Injectアノテーション標準化され(JSR 330)、SpringやGuiceなどの他の多くのツールがこれに対応していることを知って喜ぶでしょう。 したがって、別のDIフレームワークに切り替えても、アプリケーション内の多くのクラスを変更する必要はありません。



次のステップでは、必要なオブジェクトを返すメソッドを宣言して、 Componentアノテーションを持つインターフェースを作成する必要があります。 ここでは、プロジェクトの各クラスのメソッドを宣言する必要はありません。アプリケーションのエントリポイントで直接使用されるクラスのメソッドのみが必要です。 この例では、 mainメソッドにWeatherReporterのみが必要なので、インターフェイスで宣言するメソッドは1つだけです。



 @Component public interface AppComponent { WeatherReporter getWeatherReporter(); }
      
      





残っているのは、Daggerをビルドシステムに統合することだけです。 Gradleを使用する場合、 build.gradleに新しい依存関係を追加するだけです。



 plugins { id "net.ltgt.apt" version "0.7" } dependencies { apt 'com.google.dagger:dagger-compiler:2.6' compile 'com.google.dagger:dagger:2.6' }
      
      





すべて、プロジェクトをコンパイルして実行できます。 自分で試してみたい場合は、この例のソースコードをGithub入手できます



結論として。 注:以前と同様、Daggerを使用せずに任意のクラスを使用できます。 たとえば、ユニットテストなどでnew演算子を使用してLoggerを手動で初期化できます。 Daggerは言語の動作を変更せず、オブジェクトを初期化する便利なクラスを生成するだけです。



このシリーズの次の記事では、同じ依存関係を多くのクラスで使用できる場合、または何らかの理由でクラスに注釈を付けられない場合に何が起こるかを説明します。



何を読む



依存性注入が役に立たない手法のように思える場合、またはDIがテストを簡素化する例をもっと見たい場合は、MiškoHevery、Russ Ruffer、およびJonathan Wolterによる素晴らしいWrite Testable Codepdf )マニュアルをチェックしてください。



依存性注入の理論とそのバリエーションにもっと興味があるなら、Martin FowlerのInversion of Control ContainersとDependency Injectionパターンエッセイ(ロシア語に翻訳- パート1パート2 )をお勧めします。



継続するには...



オリジナル記事



All Articles