依存性注入 (DI) は、依存性の作成と管理の制御がアプリケーションから外部エンティティに移される制御の反転 (IoC) を実装するために使用される設計パターンです。これにより、よりモジュール化され、テスト可能で、保守しやすいコードを作成できます。これは、オブジェクトの作成責任がコードの他の部分に転送される手法です。これにより疎結合が促進され、コードがよりモジュール化され、管理しやすくなります。
クラスが適切に機能するには、他のクラスへの参照が必要になることがよくあります。たとえば、 Book
クラスを必要とするLibrary
クラスを考えてみましょう。これらの必要なクラスは依存関係と呼ばれます。Library クラスはBook
Library
のインスタンスがないと動作しません。
クラスが必要なオブジェクトを取得するには、主に次の 3 つの方法があります。
Library
クラスはBook
クラスの独自のインスタンスを作成し、初期化します。Context
やgetSystemService()
などの一部の Android API は、このように動作します。Library
コンストラクターは、 Book
インスタンスをパラメーターとして受け取ります。3 番目のオプションは依存性注入です。DI を使用すると、クラス インスタンスが依存関係を自ら取得するのではなく、クラスの依存関係を提供します。
DI がない場合、独自のBook
依存関係を作成するLibrary
次のようになります。
class Library { private Book book = new Book(); void open() { book.read(); } } public class Main { public static void main(String[] args) { Library library = new Library(); library.open(); } }
これは、 Library
クラスが独自のBook
を構築するため、DI の例ではありません。次の理由で問題が発生する可能性があります。
Library
とBook
密結合されています。Library Library
インスタンスはBook
の 1 つのタイプを使用するため、サブクラスや代替実装を使用することが困難になります。Book
への強い依存により、テストはより困難になります。 Library
Book
の実際のインスタンスを使用するため、異なるテスト ケースに合わせてBook
を変更するテスト ダブルの使用が妨げられます。DI を使用すると、 Library
の各インスタンスが独自のBook
オブジェクトを構築する代わりに、コンストラクターのパラメーターとしてBook
オブジェクトを受け取ります。
class Library { private Book book; Library(Book book) { this.book = book; } void open() { book.read(); } } public class Main { public static void main(String[] args) { Book book = new Book(); Library library = new Library(book); library.open(); }
メイン関数はLibrary
使用します。Library Library
Book
に依存しているため、アプリはBook
のインスタンスを作成し、それを使用してLibrary
のインスタンスを構築します。この DI ベースのアプローチの利点は次のとおりです。
Library
の再利用性: Book
のさまざまな実装をLibrary
に渡すことができます。たとえば、 Library
で使用するEBook
というBook
の新しいサブクラスを定義するとします。DI を使用すると、 EBook
のインスタンスをLibrary
に渡すだけで、それ以上の変更なしで動作します。Library
の簡単なテスト: テスト ダブルを渡してさまざまなシナリオをテストできます。NotificationService
クラスがNotification
クラスに依存するシナリオを考えてみましょう。DI がない場合、 NotificationService
はNotification
のインスタンスを直接作成するため、異なるタイプの通知を使用したり、さまざまな通知実装でサービスをテストしたりすることが難しくなります。
DI を説明するために、この例をリファクタリングしてみましょう。
interface Notification { void send(); } class EmailNotification implements Notification { @Override public void send() { // Send email notification } } class SMSNotification implements Notification { @Override public void send() { // Send SMS notification } } class NotificationService { void sendNotification(Notification notification) { notification.send(); } }
現在、 NotificationService
特定のクラスではなくNotification
インターフェースに依存しています。これにより、 Notification
のさまざまな実装を相互に使用できます。使用する実装は、 sendNotification
メソッドを通じて設定できます。
NotificationService service = new NotificationService(); service.sendNotification(new EmailNotification()); service.sendNotification(new SMSNotification());
DI には主に 3 つのタイプがあります。
class NotificationService { private final Notification notification; public NotificationService(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(new EmailNotification()); service.sendNotification(); } }
3. フィールド インジェクション (またはセッター インジェクション) : アクティビティやフラグメントなどの特定の Android フレームワーク クラスはシステムによってインスタンス化されるため、コンストラクター インジェクションは不可能です。フィールド インジェクションでは、クラスの作成後に依存関係がインスタンス化されます。
class NotificationService { private Notification notification; public Notification getNotification() { return notification; } public void setNotification(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(); service.setNotification(new EmailNotification()); service.sendNotification(); } }
4. メソッド インジェクション: 依存関係はメソッドを通じて提供され、多くの場合@Inject
アノテーションが使用されます。
前の例では、ライブラリを使用せずに、さまざまなクラスの依存関係を手動で作成、提供、管理しました。このアプローチは、手動の依存関係の注入と呼ばれます。単純なケースでは機能しますが、依存関係とクラスの数が増えると面倒になります。手動の依存関係の注入には、いくつかの欠点があります。
ライブラリは依存関係を作成して提供することで、このプロセスを自動化できます。これらのライブラリは、次の 2 つのカテゴリに分類されます。
Dagger は、Google が管理する Java、Kotlin、Android 向けの人気の依存性注入ライブラリです。Dagger は依存性グラフを作成および管理することで、アプリの DI を簡素化します。完全に静的なコンパイル時の依存性を提供し、Guice などのリフレクション ベースのソリューションに関連する開発およびパフォーマンスの問題の多くに対処します。
これらのフレームワークは実行時に依存関係を接続します。
これらのフレームワークは、コンパイル時に依存関係を接続するコードを生成します。
依存性注入の代替として、サービス ロケーター パターンがあります。この設計パターンは、クラスを具体的な依存性から分離するのにも役立ちます。依存性を作成して保存し、必要に応じて提供する、サービス ロケーターと呼ばれるクラスを作成します。
object ServiceLocator { fun getProcessor(): Processor = Processor() } class Computer { private val processor = ServiceLocator.getProcessor() fun start() { processor.run() } } fun main(args: Array<String>) { val computer = Computer() computer.start() }
サービス ロケーター パターンは、依存関係の使用方法において依存性の注入とは異なります。サービス ロケーター パターンでは、クラスが必要な依存関係を要求しますが、依存性の注入では、アプリが必要なオブジェクトを積極的に提供します。
Dagger 2 は、Android 向けの人気の DI フレームワークです。コンパイル時のコード生成を使用し、高いパフォーマンスで知られています。Dagger 2 は、依存関係を処理するために必要なコードを生成することで依存性注入のプロセスを簡素化し、定型句を減らして効率を向上させます。
Dagger 2 は、Android での依存性注入のためのアノテーションベースのライブラリです。主なアノテーションとその目的は次のとおりです。
ApiClient
を提供できます。@Module
と@Inject
を橋渡しするインターフェース。すべてのモジュールが含まれ、アプリケーションのビルダーを提供します。@Provides
に似ていますが、より簡潔です。Dagger はプロジェクトの依存関係グラフを生成し、必要に応じて依存関係を取得する場所を決定できます。これを有効にするには、インターフェースを作成し、 @Component
で注釈を付ける必要があります。
@Component
インターフェース内で、必要なクラス (例: BookRepository
) のインスタンスを返すメソッドを定義します。 @Component
アノテーションは、Dagger が公開する型を満たすために必要なすべての依存関係を含むコンテナーを生成するように Dagger に指示します。このコンテナーは Dagger コンポーネントと呼ばれ、Dagger が提供できるオブジェクトのグラフとその依存関係が含まれています。
LibraryRepository
に関する例を考えてみましょう。
LibraryRepository
コンストラクターに@Inject
注釈を追加して、Dagger がLibraryRepository
のインスタンスを作成する方法を認識できるようにします。 public class LibraryRepository { private final LocalLibraryDataSource localDataSource; private final RemoteLibraryDataSource remoteDataSource; @Inject public LibraryRepository(LocalLibraryDataSource localDataSource, RemoteLibraryDataSource remoteDataSource) { this.localDataSource = localDataSource; this.remoteDataSource = remoteDataSource; } }
2. 依存関係に注釈を付ける: 同様に、依存関係のコンストラクター ( LocalLibraryDataSource
およびRemoteLibraryDataSource
) に注釈を付けて、Dagger が依存関係の作成方法を認識できるようにします。
public class LocalLibraryDataSource { @Inject public LocalLibraryDataSource() { // Initialization code } } public class RemoteLibraryDataSource { private final LibraryService libraryService; @Inject public RemoteLibraryDataSource(LibraryService libraryService) { this.libraryService = libraryService; } }
3. コンポーネントを定義する: 依存関係グラフを定義するために、 @Component
アノテーションが付けられたインターフェースを作成します。
@Component public interface ApplicationComponent { LibraryRepository getLibraryRepository(); }
プロジェクトをビルドすると、Dagger によって、通常はDaggerApplicationComponent
という名前のApplicationComponent
インターフェースの実装が生成されます。
生成されたコンポーネントを使用して、依存関係が自動的に注入されたクラスのインスタンスを取得できるようになりました。
public class MainApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.create(); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } }
アクティビティまたはフラグメントでは、 LibraryRepository
インスタンスを取得できます。
public class MainActivity extends AppCompatActivity { @Inject LibraryRepository libraryRepository; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MainApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected libraryRepository } }
1. モジュール
∘ モジュールの主要概念
∘ コンポーネントにモジュールを含める
2. スコープ
3. コンポーネント
4. コンポーネントの依存関係
5. ランタイムバインディング
Dagger 2 のモジュールは、コンポーネントに依存関係を提供する@Module
アノテーションが付けられたクラスです。モジュールには、依存関係の作成方法と提供方法を指定するために@Provides
または@Binds
アノテーションが付けられたメソッドが含まれています。モジュールは、アプリケーションに必要なオブジェクトの作成を整理および管理するために不可欠です。
@Provides
よりも簡潔で、モジュールが抽象クラスの場合に使用されます。モジュールの例
@Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } @Provides @Singleton OkHttpClient provideOkHttpClient() { return new OkHttpClient.Builder() .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) .build(); } }
この例では、 NetworkModule
@Module
アノテーションが付けられたクラスです。このクラスには、 Retrofit
とOkHttpClient
のインスタンスを作成して返す、 @Provides
アノテーションが付けられた 2 つのメソッドが含まれています。
@Binds の使用
インターフェースとその実装がある場合は、 @Binds
を使用して実装をインターフェースにバインドできます。これは、 @Provides
を使用するよりも簡潔です。
public interface ApiService { void fetchData(); } public class ApiServiceImpl implements ApiService { @Override public void fetchData() { // Implementation } } @Module public abstract class ApiModule { @Binds abstract ApiService bindApiService(ApiServiceImpl apiServiceImpl); }
この例では、 ApiModule
@Module
アノテーションが付けられた抽象クラスです。bindApiService メソッドには@Binds
アノテーションが付けられ、 ApiServiceImpl
bindApiService
ApiService
バインドします。
モジュールは、提供する機能に基づいて整理できます。たとえば、ネットワーク操作、データベース操作、UI 関連の依存関係ごとに個別のモジュールを用意できます。
例:
Retrofit
やOkHttpClient
などのネットワーク関連の依存関係を提供します。RoomDatabase
のようなデータベース関連の依存関係を提供します。ViewModel
やPresenter
などの UI 関連の依存関係を提供します。モジュールは、それを必要とするクラスに依存関係を提供するためにコンポーネントに含まれています。設定方法は次のとおりです。
アプリケーションコンポーネント.java:
@Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }
この例では、 ApplicationComponent
には、アプリケーションへの依存関係を提供するためにNetworkModule
とDatabaseModule
が含まれています。
Dagger 2 のスコープは、依存関係のライフサイクルを定義する注釈です。スコープにより、依存関係の単一のインスタンスが指定されたスコープ内で作成され、共有されることが保証されます。これにより、メモリを効率的に管理し、依存関係が適切な場所で再利用されることが保証されます。
1. シングルトンスコープ
定義: @Singleton
スコープは、依存関係の単一のインスタンスが作成され、アプリケーションのライフサイクル全体を通じて共有されることを保証します。
このスコープは通常、ネットワーク クライアント、データベース インスタンス、共有設定など、アプリケーション全体で共有する必要がある依存関係に使用されます。
例:
@Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }
この例では、 @Singleton
アノテーションにより、 NetworkModule
およびDatabaseModule
によって提供されるRetrofit
インスタンスとDatabase
インスタンスがシングルトンであり、アプリケーション全体で共有されることが保証されます。
2. 活動範囲
定義: @ActivityScope
(カスタム スコープ) は、アクティビティのライフサイクル内で依存関係の単一インスタンスが作成され、共有されることを保証します。
このスコープは、プレゼンターやビュー モデルなど、アクティビティに固有で、アクティビティが再作成されるたびに再作成する必要がある依存関係に役立ちます。
例:
@Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope { } @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }
この例では、 @ActivityScope
アノテーションにより、 ActivityModule
によって提供される依存関係がアクティビティのライフサイクルにスコープされることが保証されます。
3. フラグメントスコープ
定義: @FragmentScope
(別のカスタム スコープ) は、フラグメントのライフサイクル内で依存関係の単一のインスタンスが作成され、共有されることを保証します。
使用例: このスコープは、フラグメント固有のプレゼンターやビュー モデルなど、フラグメントが再作成されるたびに再作成する必要がある、フラグメントに固有の依存関係に役立ちます。
例:
@Scope @Retention(RetentionPolicy.RUNTIME) public @interface FragmentScope { } @FragmentScope @Component(dependencies = ActivityComponent.class, modules = FragmentModule.class) public interface FragmentComponent { void inject(MyFragment myFragment); }
この例では、 @FragmentScope
アノテーションにより、 FragmentModule
によって提供される依存関係がフラグメントのライフサイクルにスコープされることが保証されます。
コンポーネントの依存関係により、あるコンポーネントが別のコンポーネントに依存することが可能になり、依存関係の再利用が可能になります。コンポーネントの依存関係には、主に次の 2 つの種類があります。
1. アプリケーションコンポーネント
定義: アプリケーション コンポーネントは、アプリケーション全体で必要な依存関係を提供します。通常は、依存関係がアプリケーション全体で共有されるように、 @Singleton
でスコープ設定されます。
このコンポーネントは、ネットワーク クライアント、データベース インスタンス、共有設定など、グローバルに使用可能にする必要がある依存関係に使用されます。
例:
@Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }
この例では、 ApplicationComponent
、アプリケーション全体で共有されるRetrofit
インスタンスとDatabase
インスタンスを提供する役割を担っています。
2. アクティビティコンポーネント
定義: アクティビティ コンポーネントは、特定のアクティビティ内で必要な依存関係を提供します。通常、アクティビティが再作成されるたびに依存関係が再作成されるように、 @ActivityScope
などのカスタム スコープでスコープ設定されます。
このコンポーネントは、プレゼンターやビュー モデルなど、アクティビティに固有の依存関係に使用されます。
例:
@ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }
この例では、 ActivityComponent
ApplicationComponent
に依存し、 MainActivity
に固有の依存関係を提供します。
コンポーネントの依存関係により、あるコンポーネントが別のコンポーネントに依存することが可能になり、依存関係の再利用が可能になります。コンポーネントの依存関係には、主に次の 2 つの種類があります。
1. サブコンポーネント:
サブコンポーネントは別のコンポーネントの子であり、親の依存関係にアクセスできます。サブコンポーネントは親コンポーネント内で定義され、そのスコープを継承できます。
例:
@ActivityScope @Subcomponent(modules = ActivityModule.class) public interface ActivitySubcomponent { void inject(MainActivity mainActivity); }
この例では、 ActivitySubcomponent
親コンポーネントのサブコンポーネントであり、その依存関係にアクセスできます。
2. 依存属性
これにより、コンポーネントはサブコンポーネントにならずに別のコンポーネントに依存できるようになります。依存コンポーネントは、親コンポーネントによって提供される依存関係にアクセスできます。
例:
@ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }
この例では、 ActivityComponent
ApplicationComponent
に依存しており、その依存関係にアクセスできます。
Dagger 2 のランタイム バインディングとは、必要なコンテキストに基づいて実行時に作成および管理される依存関係の提供を指します。
1. アプリケーションのコンテキスト
定義: アプリケーション コンテキストは、アプリケーション全体のライフサイクルに関連付けられたコンテキストです。アプリケーション自体と同じ期間存続する必要がある依存関係に使用されます。
アプリケーション全体で共有され、アクティビティやフラグメントごとに再作成する必要のない依存関係。例としては、ネットワーク クライアント、データベース インスタンス、共有設定などがあります。
例:
@Module public class AppModule { private final Application application; public AppModule(Application application) { this.application = application; } @Provides @Singleton Application provideApplication() { return application; } @Provides @Singleton Context provideApplicationContext() { return application.getApplicationContext(); } }
この例では、 AppModule
アプリケーション コンテキストをシングルトン依存関係として提供します。provideApplicationContext メソッドは、提供されるコンテキストがアプリケーションのライフサイクルに関連付けられていることprovideApplicationContext
保証します。
2. アクティビティのコンテキスト
定義: アクティビティ コンテキストは、特定のアクティビティのライフサイクルに関連付けられたコンテキストです。アクティビティ自体と同じ期間存続する必要がある依存関係に使用されます。
アクティビティに固有の依存関係であり、アクティビティが再作成されるたびに再作成する必要があります。例としては、ビュー モデル、プレゼンター、UI 関連の依存関係などがあります。
例:
@Module public class ActivityModule { private final Activity activity; public ActivityModule(Activity activity) { this.activity = activity; } @Provides @ActivityScope Activity provideActivity() { return activity; } @Provides @ActivityScope Context provideActivityContext() { return activity; } }
この例では、 ActivityModule
アクティビティ コンテキストをスコープ指定された依存関係として提供します。provideActivityContext メソッドは、提供されたコンテキストがアクティビティのライフサイクルに関連付けられていることprovideActivityContext
保証します。
これらのランタイム バインディングを使用するには、コンポーネントに対応するモジュールを含める必要があります。
アプリケーションコンポーネント:
@Singleton @Component(modules = {AppModule.class, NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); Context getApplicationContext(); }
アクティビティコンポーネント:
@ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); Context getActivityContext(); }
コンテキストの挿入
コンポーネントとモジュールを設定したら、必要に応じてコンテキストをクラスに挿入できます。
アクティビティの例:
public class MainActivity extends AppCompatActivity { @Inject Context activityContext; @Inject Context applicationContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ApplicationComponent appComponent = ((MyApplication) getApplication()).getApplicationComponent(); ActivityComponent activityComponent = DaggerActivityComponent.builder() .applicationComponent(appComponent) .activityModule(new ActivityModule(this)) .build(); activityComponent.inject(this); // Use the injected contexts Log.d("MainActivity", "Activity Context: " + activityContext); Log.d("MainActivity", "Application Context: " + applicationContext); } }
この例では、 MainActivity
依存性の注入を通じてアクティビティ コンテキストとアプリケーション コンテキストの両方を受け取ります。これにより、アクティビティは依存性の特定のニーズに基づいて適切なコンテキストを使用できるようになります。
プロジェクトで Dagger 2 を使用するには、 build.gradle
ファイルに次の依存関係を追加する必要があります。
dependencies { implementation 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x' }
2.x
を Dagger 2 の最新バージョンに置き換えます。
依存関係を提供するモジュールを作成します。たとえば、 Retrofit
インスタンスを提供するNetworkModule
次のようになります。
@Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } }
モジュールと依存関係を必要とするクラスをブリッジするコンポーネントを作成します。
@Singleton @Component(modules = {NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }
コンポーネントを使用してクラスに依存関係を挿入します。たとえば、 Application
クラスでは次のようになります。
public class MyApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.builder() .networkModule(new NetworkModule()) .build(); applicationComponent.inject(this); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } }
これで、注入された依存関係をクラスで使用できるようになります。たとえば、 Activity
では次のようになります。
public class MainActivity extends AppCompatActivity { @Inject Retrofit retrofit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MyApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected Retrofit instance // ... } }
このトピックを要約してみましょう: