☺️

依存性と依存性の注入・逆転を理解する

2023/09/18に公開

はじめに

依存性とは

依存性とは「依存するオブジェクト」のことです。

と言われても全然ピンと来ないと思います。

そこで、今回は簡単なプログラムを用いて、依存性が発生しているケースと、解消方法である依存性の注入・逆転について、解説していきたいと思います。

依存性が発生しているケース

  • NameRepository はユーザ名を返します。
name_repository.dart
class NameRepository {
  String getName() {
    return 'Tom'
   }
}
  • NameModel は内部で NameRepository を使用してユーザの名前を取得します。
name_model.dart
import 'name_repository.dart';

class NameModel {
  //内部でNameRepositoryをインスタンス化している
  final nameRepository _nameRepository = NameRepository();
  
  void callHallo() {
    final String name = _nameRepository.getName();
    print('Hello $name');
  }
}
  • main() の処理では NameModel の callHello() メソッドを実行します。
main.dart
import 'name_model.dart';

void main() {
  final nameModel nameModel = NameModel();
  nameModel.callHello();
}

この場合NameModel はNameRepositoryがないと動きないため、NameModel はNameRepositoryに依存していると言えます。
これが依存性と呼ばれるものになります。
この依存性は何が問題となるのか、次は私が製造業の出身であることから、部品メーカーとその部品を使って製品を作っている自動車のメーカーを例に解説したいと思います。

依存性の問題を解説

部品メーカーは独自に部品をつくっていて、その部品を大手のメーカーに提供しています。

自動車メーカーは渡された部品を使って、どういう製品を作るか設計して実装(製造)を行なっています。

このような場合、部品メーカーとの関係が悪くなったり、また部品メーカーが倒産した場合、自動車メーカーは製品を作ることができなくなってしまいます。

なぜならば、この場合自動車メーカーは特定の部品メーカーに依存しているためです。

依存性の問題を解決するために

ではどうすれば、自動車メーカーは依存するせずに製品を製造することができるのでしょうか。
そのためには、

  1. 自動車メーカーはまず作りたい車を設計する。
  2. その車に必要となる部品の仕様書を作成し、部品メーカーにそれを作るよう依頼する。
  3. 部品メーカーは決められた仕様に基づいて部品を作成し、自動車メーカーに納品する。

このような構図になれば、自動車メーカーはもはや特定の部品メーカーに依存していません。
仕様を満たす部品をより安くつくることのできる部品メーカーが見つかったのであれば、そちらに切り替えることも可能になります。
またこの場合、部品メーカーは自動車メーカーに依存することになるため、依存性の構図が逆転しています。
これを依存性の逆転と言います。

依存性の逆転をプログラムで実装

部品メーカーと自動車メーカーの例で依存性と依存性の逆転がわかったところで、次は先ほどのプログラムの依存性を逆転してみましょう。
依存性を逆転させるためには、依存性を注入するといった段階を踏む必要があります。

name_repository.dart
class NameRepository {
  String getName() {
    return 'Tom';
  }
}
name_model.dart
import 'name_repository.dart';

class NameModel {
  NameModel(this._nameRepository);
  final NameRepository _nameRepository;
  
  void callHello() {
    final String name = _nameRepository.getName();
    print('Hello,$name');
  }
}
  • これによりNameModel は自身が必要とするNameRepositoryを外部から受け取るようになります。
main.dart
import 'name_model.dart';
import 'name_repository.dart';

void main() {
  final NameRepository nameRepository = NameRepository();
  final NameModel name = NameModel(nameRepository);
  name.callHello();
}
  • main() の処理で NameRepository をインスタンス化し NameModel に渡しています。
  • これにより NameModel に例えば DummyNameRepository を渡すこともできるようになります。

初期のコードでは、NameModel が自分でNameRepositoryをインスタンス化していましたが、
これをNameModelを使用するmain()から渡すことで依存性の注入をすることができます。
しかし、引き続き NameModel は NameRepository に依存していているため、修正が必要です。

依存性の逆転をプログラムで実装(続き)

name_repository.dart
abstract class INameRepository {
  String getName();
}

class NameRepository implements INameRepository {
  
  String getName() {
    return 'Tom';
  }
}
  • NameRepository のインターフェースである INameRepository を定義しました。
name_model.dart
import 'name_repository.dart';

class NameModel {
  NameModel(this._nameRepository);

  final INameRepository _nameRepository;

  
  void callHello() {
    final String name = _nameRepository.getName();
    print('Hello,$name');
  }
}

これによりNameModel  は  NameRepository  ではなく  INameRepository  というインターフェースに依存するようになりました。

main.dart
import 'name_model.dart';
import 'name_repository.dart';

void main() {
  final INameRepository nameRepository = NameRepository();
  final NameModel name = NameModel(nameRepository);
  name.callHello();
}

しかしこの場合、name_repositpry.dartのファイルを削除した場合、name_model.dartでエラーが発生します。 これはname_model.dartのコードは、name_repositpry.dartに依存しているためです。
そのため、引き続き下記のように修正します。

name_model.dart
abstract class INameRepository {
  String getName();
}

class NameModel {
  NameModel(this._nameRepository);

  final INameRepository _nameRepository;

  
  void callHello() {
    final String name = _nameRepository.getName();
    print('Hello$name');
  }
}
  • INameRepositoryの定義をname_model.dartに移動させました。
name_repository.dart
import 'name_model.dart';

class NameRepository implements INameRepository {
  
  String getName() {
    return 'Tom';
  }
}
  • NameRepository は name_model.dart からインターフェースを import するようになりました。
main.dart
import 'name_model.dart';
import 'name_repository.dart';

void main() {
  final INameRepository nameRepository = NameRepository();
  final NameModel name = NameModel(nameRepository);
  name.callHello();
}
  • NameRepository は name_model.dart で定義されている INameRepository に依存するようになり、依存性が逆転しました。

これにより、name_repository.dartのファイルが削除されてもname_model.dartを修正する必要がなくなります。
逆に name_model.dart が削除されると name_repository.dartではエラーが起きるようになり、最初と依存関係が逆転していることがわかります。

最後に

現実世界に例えることで、自分はとても腑に落ちることができました。
今回の記事を見て、誰かの力になれればとても嬉しいです。
長文となりましたが、ここまで見てくださりありがとうございます。

Discussion