依存性の注入やDIコンテナってよく聞くのですが、いまいちメリットが良くわからない!
ということが多いので、AndroidのDIコンテナである、Daggerについてまとめました。
依存性の注入(Dependency injection)
依存性の注入(いそんせいのちゅうにゅう、英: Dependency injection)とは、
コンポーネント間の依存関係をプログラムのソースコードから排除し、外部の設定ファイルなどで注入できるようにするソフトウェアパターンである。
英語の頭文字からDIと略される。
https://ja.wikipedia.org/wiki/%E4%BE%9D%E5%AD%98%E6%80%A7%E3%81%AE%E6%B3%A8%E5%85%A5
と説明されているのですが、実際どのようなメリットがあるのかよくわかりません。
依存性の注入が無いAndroidのコード例
retrofitを使って、APIに問い合わせするという例。
class ArticleActivity : AppCompatActivity() {
private val disposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val articleId = intent.getIntExtra(ARTICLE_ID, 0)
val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
val service = retrofit.create(ArticlesService::class.java)
service.article(articleId)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.doOnSubscribe { disposable.add(it) }
.subscribe({
Log.v("ID", it.id.toString())
}, {
it.printStackTrace()
})
}
}
class UserActivity : AppCompatActivity() {
private val disposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val articleId = intent.getIntExtra(USER_ID, 0)
val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
val service = retrofit.create(UserService::class.java)
service.article(articleId)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.doOnSubscribe { disposable.add(it) }
.subscribe({
Log.v("ID", it.id.toString())
}, {
it.printStackTrace()
})
}
}
このコードで問題点を上げるとするならば、
- オブジェクト(インスタンス)の生成が多い
- Retrofit => dispossable => service
- オブジェクトの生成を列挙するという手続き的な書き方になってしまう
- 複数のActivityで似たようなコードを使う
- 変更に弱くなってしまい、保守が大変になる
DIを使わずに書いた場合
以下のようなラッパークラスを作って対応することができます。
class ArticleActivity : AppCompatActivity() {
private val disposable = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val articleId = intent.getIntExtra(ARTICLE_ID, 0)
ArticleClient().create().article(articleId)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.doOnSubscribe { disposable.add(it) }
.subscribe({
Log.v("ID", it.id.toString())
}, {
it.printStackTrace()
})
}
}
class ArticleClient {
fun create() {
val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
val service = retrofit.create(ArticleService::class.java)
}
}
こう書くことによって、
retrofitとserviceの生成をArticleClientにまとめることができました。
そのため、オブジェクトの生成は減りました。
これによって保守性は少し上がったかもしれません。
ですが、結局オブジェクトの生成をなくすことができないです。
DI(依存性の注入)ではこの、オブジェクトの作成すらコードから排除しようという考え方です。
DIを使って書く
ここでDIとは一体何かを説明します。
日本語訳では依存性の注入と呼ばれていますが、実際はオブジェクトの注入が正しいです。
オブジェクトの注入とは何かというと、オブジェクトの作成をクラス内で行わずに、外部で定義して持ってくるということです。
導入方法については以下を参照してみて下さい
https://antonioleiva.com/dagger-android-kotlin/
導入すると以下のようなコードになると思います
class ArticleActivity : AppCompatActivity() {
val component by lazy { app.component.plus(HomeModule(this)) }
@Inject lateinit var disposable: CompositeDisposable
@Inject lateinit var service: ArticleService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
component.inject(this)
val articleId = intent.getIntExtra(ARTICLE_ID, 0)
service.article(articleId)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.doOnSubscribe { disposable.add(it) }
.subscribe({
Log.v("ID", it.id.toString())
}, {
it.printStackTrace()
})
}
}
@Module
class AppModule {
@Provides
fun provideCompositeDisposable(): CompositeDisposable = CompositeDisposable()
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
@Provides
@Singleton
fun provideArticleService(retrofit: Retrofit): ArticleService
= retrofit.create(ArticleService::class.java)
}
オブジェクトの注入について
daggerでは@Providesで書かれたオブジェクトを@Injectがついた変数に入れるという機能があります。
上記の例のコードでは
@Inject lateinit var disposable: CompositeDisposable
は
@Provides
fun provideCompositeDisposable(): CompositeDisposable = CompositeDisposable()
が呼ばれます。
つまり、
val disposable: CompositeDisposable = CompositeDisposable()
と同じことがされます。
この機能によって、複数のActivityで定義されたオブジェクトの生成の共通化 などが行なえます。
オブジェクトの注入による省略
次に、以下のコードについて注目します
@Provides
@Singleton
fun provideArticleService(retrofit: Retrofit): ArticleService
= retrofit.create(ArticleService::class.java)
ここのretrofit: Retrofitという引数は
@Provides
@Singleton
fun provideRetrofit(): Retrofit {
Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
から参照されます。
なので、変更前のコードでは、ArticleServiceのオブジェクトを生成するために、Retrofitのオブジェクトを生成していましたが、
Daggerを使うことによって、オブジェクトの生成のためのオブジェクトの生成の生成を省略できます
まとめ
Daggerを使うことによって
- オブジェクトの注入(依存性の注入)ができる
- オブジェクトの注入によってオブジェクトの生成をコードから省略できる
- オブジェクトの生成に必要なオブジェクトを生成するというコードも省略できる
というメリットが得られます。