サイトロゴ

Enjoy Creating
Web & Mobile Apps

MENU BOX
WEB
MOBILE
OPEN

ホーム

 > 

 > 

【Kotlin】世界一易しい依存性注入(DI: Dependency Injection)の基本

【Kotlin】世界一易しい依存性注入(DI: Dependency Injection)の基本

この記事にはプロモーションが含まれています。

Android開発において欠かせないテクニックの一つに、依存性注入(DI: Dependency Injection)というものがあります。(以下 “DI” と記載)

DIは効率的なアプリ開発を行う上で重要な概念ですが、やや応用的であるため最初はやや理解が難しいと感じられるかもしれません。

そこで今回は、『世界一易しくDIについて説明する』をコンセプトに、DIの基本中の基本にだけ的を絞って解説してみようと思います。

DIの深いところには触れませんが、その代わり難しいことは一切抜きにします。プログラミング初心者の方にとっても、できるだけ理解し易いようにまとめました。

この記事を読むことで分かること
  • ・DIとは何か?
  • ・なぜDIを行うのか?
  • ・DIのシンプルな例

– 目次 –

そもそも依存性とは?

DIは『依存性注入』と訳されるように、『依存性』がキーワードとなっています。そこで、まず最初に理解すべきなのは、『そもそも依存性って何?』ということです

たとえば、クラスBのインスタンスを作成する際に、クラスAのインスタンスが必要となる場合を考えてみましょう。クラスAのインスタンスから得られるプロパティやメソッドを、クラスBの内部で利用したい場合などが考えられますね。

イメージとしては下の図のような関係になります。

クラスBがクラスAに依存していることを表すイメージ図

この状況を下のように順序立てて整理すると、依存性とは何かが見えてきます。

  1. クラスBのインスタンスを作成するにはクラスAのインスタンスが必要
  2. クラスBはクラスA(とそのインスタンス)の存在無しに役割を果たせない
  3. クラスBはクラスAに依存性がある(依存関係が成立する)

「そんなの当たり前じゃないか」と思うかもしれませんが、この『当たり前』をしっかり理解しておくことが、DIを理解する上でとても大切です。

さて、このときクラスBのインスタンスを作成する方法の一つとして、クラスBの内部でクラスAのインスタンスを作成する方法があります。イメージとしては下の図のようになります。

クラスBの内部でクラスAのインスタンスを作成し、クラスBのインスタンスを作成していることを表すイメージ図

この状態をKotlinコードで表すと次のようになります。(※各クラスのプロパティやメソッドに特に意味はありません。ここでは構造のみに注目して下さい。)

Kotlin

class ClassA {
    val propA = "The property of ClassA"
}

class ClassB {
    val instanceA = ClassA()
    fun methodB() {
        println(instanceA.propA)
    }
}

クラスBの内部でクラスAのインスタンスが作成され、クラスBのメソッドの中でクラスAのプロパティにアクセスしています。この構造はシンプルで分かりやすいのがメリットですが、一方で様々なデメリットも抱えています。

例えば、クラスAに依存するクラスCが新たに追加された場合を考えてみましょう。この時、当然ですがクラスCでも改めてクラスAのインスタンスを作成しなければならなくなります。イメージ図とコード例は下のようになります。

これまでの例に、新しくクラスAに依存するクラスCが追加された場合のイメージ図

Kotlin

class ClassA {
    val propA = "The property of ClassA"
}

class ClassB {
    val instanceA = ClassA()
    fun methodB() {
        println(instanceA.propA)
    }
}

class ClassC {
    val instanceA = ClassA()
    fun methodC() {
        println(instanceA.propA)
    }
}

この例のように、いちいち各クラスの内部で個別にクラスAのインスタンスを作成しなければならないのは、ちょっと無駄が多くて手間に思えてきますよね。

ではさらに、クラスAのインスタンスを作成するのに、コンストラクタに値を渡さなくてはならなくなったらどうでしょう?例えば、IDとして整数値を渡す必要が出てきた場合を考えてみましょう。

コードは次のようになります。

Kotlin

class ClassA (private val id: Int) {
    val propA = "ID:$id The property of ClassA"
}

class ClassB {
    val instanceA = ClassA(10)
    fun methodB() {
        println(instanceA.propA)
    }
}

class ClassC {
    val instanceA = ClassA(10)
    fun methodC() {
        println(instanceA.propA)
    }
}

クラスAのインスタンスを作成するのに必要な手順が追加されたことで、各クラスの内部でクラスAのインスタンスを作成するのが余計に面倒になってきました。

クラスAのコンストラクタに渡す必要のある値が増えれば増えるほど、クラスAに依存するクラスBやクラスCのコードも複雑になってしまいます。そうなるとミスが起きやすくなりますし、テストもやりづらくなってしまいます。

このように、依存関係を持つクラス間には様々な問題が発生してしまいがちです。そしてそれはプロジェクトの規模が大きくなればなるほど、無視できないレベルになります。

ではどうすればこの問題を解決できるのでしょうか?その答えの一つが、次のステップで説明する『DI(依存性注入)』です。

    POINT!
  1. ・あるクラスのインスタンスを作成するのに別のクラスのインスタンスが必要であるような場合に、『依存性がある(依存関係を持つ)』と言える!
  2. ・依存しているクラスの構造が複雑になると、それに伴って依存元のクラスも複雑になってしまう!
  3. ・このような依存関係の問題を解決するためのテクニックがDI(依存性注入)!

DI(依存性注入)のシンプルな例

前のステップで紹介したコードは、クラスの内部に依存関係を持つクラスのインスタンスを定義することで成り立っていました。そしてこの状態はクラスが直接依存するため、独立性が低いという問題を抱えています。

ではどうすれば良いかと言うと答えはシンプルで、クラスの依存関係を弱くすることでクラスの独立性を高めれば良いのです。それこそがDIであり、DIを行う主たる目的です。

ここでは前のステップで用いた例(クラスA〜クラスC)を引き続き利用しながら、シンプルなDIの具体例を確認していきましょう。

クラスAに依存するクラスBやクラスCのインスタンスを作成するには、クラスAのインスタンスが必要である…という状態ですが、このとき、必ずしもクラスBやクラスCの内部でクラスAのインスタンスを作成する必要はありません。

クラスの内部でインスタンスを作成するのではなく、『クラスAのインスタンスをコンストラクタとして受け取る』ようにすれば良いのです。イメージ図は下のようになります。

コンストラクタとしてクラスAのインスタンスを受け取っているイメージ図

このような構造にすることで、クラスBやクラスCは『クラスAのインスタンスを作成する』という役割を外部に任せることができるようになり、クラスAとの依存関係を少し弱めることができます。結果として、クラスの独立性が高まるため、テストも行いやすくなります。

コードは次のように表すことができます。

Kotlin

class ClassA (private val id: Int) {
    val propA = "ID:$id The property of ClassA"
}

val instanceA = ClassA(10) // ..1

class ClassB (private val instanceA: ClassA) {
    fun methodB() {
        println(instanceA.propA)
    }
}

class ClassC (private val instanceA: ClassA) {
    fun methodC() {
        println(instanceA.propA)
    }
}

fun main() {
    val classB = ClassB(instanceA)
    classB.methodB() // Outputs: ID:10 The property of ClassA
}

クラスBのやCのコンストラクタでクラスAのインスタンスを受け取るようにすることで、クラスAのインスタンスの作成が一箇所だけ(..1)で済むようになりました。更に、今後クラスAのコンストラクタに渡す値が増えたとしても、クラスBやCには影響を及ぼしません。

クラスBやクラスCのテストを行いたい場合は、テスト用のクラスAのインスタンスを一つ作成しておけば良いためテストの実施も容易になります。テストの容易化は、DIを行う大きなメリットの一つです。


いかがでしょうか?『DIとは何か』や『なぜDIを行うのか?』が、なんとなくイメージできたのではないかと思います。

DI(Dependency Injection)という英単語はもちろん、それを和訳した『依存性注入』という単語からも、それが何を意味しているのかイメージしづらいですが、『DI = クラス間の依存性を弱めて独立性を高める』と解釈すると分かりやすいかと思います。

なお、この記事で行ったDIは自身の手でDIパターンを導入しているため、『手動DI(Manual Dependency Injection)』と呼ばれます。

一方、フレームワークやライブラリを利用して自動的にDIを取り入れることを『自動DI(Automatic Dependency Injection)』と言います。

大規模な開発では自動DIが第一選択になるかと思いますが、DIの基本的な仕組みや考え方を理解するためには、まず手動DIから学び、手動DIを取り入れることから始めた方がスムーズかもしれません。

最後に一つ、改めて注意点をお伝えしておきます。

この記事ではあくまで『とにかく分かりやすく、DIの基本をシンプルに解説する』ことを目的としており、DIについて広く・深くカバーしているわけではありません。DIの手法や例は様々であり、必ずしも他のDIに関する記事と本記事の内容が一致するとは限らないので、ご注意ください!

    POINT!
  1. ・コンストラクタで依存クラスのインスタンスを受け取るようにすることで、クラス間の依存関係を弱めることができる!
  2. ・クラスの独立性を高めることでテストなどが行いやすくなる!
  3. ・この記事で紹介したDIはあくまで一例に過ぎず、全てをカバーしているわけではない点には注意が必要!

« »

カテゴリーリンク

著者について- author profile -

ROYDOプロフィール写真
Michihiro

モバイルアプリ(iOS・Android)ディベロッパー&デザイナー

これまでに、可読性の高いカラーパターンを自動で生成するアプリや、『第3火曜日』といった形式で通知をスケジュールできるアプリなどを制作。

サブでWebデザイン・フロントエンドエンジニアとしても活動しています。

📝ツール・言語:JavaScript/React Native/Kotlin/Android Studio/Swift/SwiftUI

🎓資格:基本情報技術者/ウェブデザイン技能検定3級

Twitterアイコン Instagramアイコン