5分で理解する依存性の注入・逆転
August 3, 2022
前半では実際のコードを用いて、依存性の注入・逆転させていく具体例を示します。
後半では、依存性の注入・逆転について言葉で解説します。
コードの具体例 #
言語は Dart です。
初期状態 #
main.dart
import 'greeting_model.dart';
void main() {
final GreetingModel greetingModel = GreetingModel();
greetingModel.sayHello();
}
greeting_model.dart
import 'user_repository.dart';
class GreetingModel {
final UserRepository _userRepository = UserRepository();
void sayHello() {
final String name = _userRepository.getName();
print('Hello, $name');
}
}
user_repository.dart
class UserRepository {
String getName() {
return 'Alice';
}
}
説明 #
main()
の処理ではGreetingModel
のsayHello()
メソッドを実行しています。GreetingModel
を見ると、内部でUserRepository
を使用してユーザの名前を取得しています。UserRepository
はユーザ名を返します。
問題点 #
GreetingModel
は内部でUserRepository
をインスタンス化しています。GreetingModel
はUserRepository
に依存しています。
STEP 1:依存性を注入する #
main.dart
import 'greeting_model.dart';
import 'user_repository.dart';
void main() {
final UserRepository userRepository = UserRepository();
final GreetingModel greeting = GreetingModel(userRepository);
greeting.sayHello();
}
greeting_model.dart
import 'user_repository.dart';
class GreetingModel {
GreetingModel(this._userRepository);
final UserRepository _userRepository;
void sayHello() {
final String name = _userRepository.getName();
print('Hello, $name');
}
}
user_repository.dart
class UserRepository {
String getName() {
return 'Alice';
}
}
説明 #
GreetingModel
は自身が必要とするUserRepository
を外部から受け取るようになりました。main()
の処理でUserRepository
をインスタンス化しGreetingModel
に渡しています。- これにより
GreetingModel
に例えばDummyUserRepository
を渡すこともできるようになりました。
問題点 #
- 引き続き
GreetingModel
はUserRepository
に依存しています。
STEP 2-1:依存性を逆転する(途中まで) #
main.dart
import 'greeting_model.dart';
import 'user_repository.dart';
void main() {
final IUserRepository userRepository = UserRepository();
final GreetingModel greeting = GreetingModel(userRepository);
greeting.sayHello();
}
greeting_model.dart
import 'user_repository.dart';
class GreetingModel {
GreetingModel(this._userRepository);
final IUserRepository _userRepository;
@override
void sayHello() {
final String name = _userRepository.getName();
print('Hello, $name');
}
}
user_repository.dart
abstract class IUserRepository {
String getName();
}
class UserRepository implements IUserRepository {
@override
String getName() {
return 'Alice';
}
}
説明 #
UserRepository
のインターフェースであるIUserRepository
を定義しました。GreetingModel
はUserRepository
ではなくIUserRepository
というインターフェースに依存するようになりました。- いわゆる「抽象に依存する」状態になりました。
STEP 2-2:依存性を逆転する(完了) #
main.dart
import 'greeting_model.dart';
import 'user_repository.dart';
void main() {
final IUserRepository userRepository = UserRepository();
final GreetingModel greeting = GreetingModel(userRepository);
greeting.sayHello();
}
greeting_model.dart
abstract class IUserRepository {
String getName();
}
class GreetingModel {
GreetingModel(this._userRepository);
final IUserRepository _userRepository;
@override
void sayHello() {
final String name = _userRepository.getName();
print('Hello, $name');
}
}
user_repository.dart
import 'greeting_model.dart';
class UserRepository implements IUserRepository {
@override
String getName() {
return 'Alice';
}
}
説明 #
IUserRepository
の定義をgreeting_model.dart
に移動させました。UserRepository
はgreeting_model.dart
からインターフェースを import するようになりました。UserRepository
はgreeting_model.dart
で定義されているIUserRepository
に依存するようになり、依存性が逆転しました。
言葉で説明 #
依存性とは #
依存性とは何を指すのか。これは「依存するオブジェクト」のことです。
初期状態のコードを例にとると GreetingModel
は UserRepository
がなければ動きませんでした。
これは GreetingModel
は UserRepository
に依存しているということです。依存性という言葉を使うのであれば GreetingModel
にとっての依存性は UserRepository
です。
依存性の注入 #
初期のコードでは GreetingModel
が自分で UserRepository
をインスタンス化していました。これを外側、つまり GreetingModel
を使用する main()
から渡すようにしたのが依存性の注入です。
依存性の逆転 #
最終形のコードになる前の場合 user_repository.dart
のファイルを削除したときに greeting_model.dart
の方でエラーが表示されます。greeting_model.dart
のコードは user_repository.dart
に依存しており、これなくして存在できないためです。
しかし最終形になると、たとえ user_repository.dart
のファイルが削除されても greeting_model.dart
を修正する必要はなくなります。
逆に greeting_model.dart
が削除されると user_repository.dart
ではエラーが起きるようになっており、最初と依存関係が逆転していることがわかります。
依存性の逆転についてもう少し解説 #
最終形のコードにて IUserRepository
が greeting_model.dart
に定義されていることに違和感があるかもしれません。しかしこれは正しい形です。(補足:IUserRepository
というインターフェース名自体は洗練の余地があります。)
依存性の逆転とは、言ってみればこういうことです。
- 依存性の逆転前:
UserRepository
にて定義された仕様にもとづいてGreetingModel
がそのコードを利用している。 - 依存性の逆転後:
GreetingModel
が、自分が必要とするコードの仕様を定義し(IUserRepository
)、その仕様に基づいたコードを外部に要求している。
現実の事例に例えるなら以下の通りです。
依存性を逆転する前 #
自動車メーカーと部品メーカーがいる。部品メーカーは独自に部品をつくっていて、それを自動車メーカーに渡している。
自動車メーカーは、渡された部品を使って、どういう車を作るか設計・実装する。
依存性を逆転した後 #
自動車メーカーはまず作りたい車を設計する。
その車に必要となる部品の仕様書を作成し、部品メーカーにそれを作るよう依頼する。
部品メーカーは決められた仕様に基づいて部品を作成し、自動車メーカーに納品する。
依存性を逆転すると何がいいのか #
コード上でみるとわかりづらいですが、現実の事例だとわかりやすかったのではないでしょうか。
依存性が逆転する前の場合、部品メーカーとの関係が悪くなったり、あるいは部品メーカー倒産した場合、自動車メーカーは車を作ることができなくなってしまいます。なぜならば、自動車メーカーは特定の部品メーカーに依存しているためです。
依存性が逆転した後ならば、自動車メーカーはもはや特定の部品メーカーに依存していません。例えば、仕様を満たす部品をより安くつくることのできる部品メーカーが見つかったのであれば、そちらに切り替えることも可能になります。