5分で理解する依存性の注入・逆転

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() の処理では GreetingModelsayHello() メソッドを実行しています。
  • GreetingModel を見ると、内部で UserRepository を使用してユーザの名前を取得しています。
  • UserRepository はユーザ名を返します。

問題点 #

  • GreetingModel は内部で UserRepository をインスタンス化しています。
  • GreetingModelUserRepository に依存しています。

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 を渡すこともできるようになりました。

問題点 #

  • 引き続き GreetingModelUserRepository に依存しています。

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 を定義しました。
  • GreetingModelUserRepository ではなく 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 に移動させました。
  • UserRepositorygreeting_model.dart からインターフェースを import するようになりました。
  • UserRepositorygreeting_model.dart で定義されている IUserRepository に依存するようになり、依存性が逆転しました。

言葉で説明 #

依存性とは #

依存性とは何を指すのか。これは「依存するオブジェクト」のことです。

初期状態のコードを例にとると GreetingModelUserRepository がなければ動きませんでした。

これは GreetingModelUserRepository に依存しているということです。依存性という言葉を使うのであれば 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 ではエラーが起きるようになっており、最初と依存関係が逆転していることがわかります。

依存性の逆転についてもう少し解説 #

最終形のコードにて IUserRepositorygreeting_model.dart に定義されていることに違和感があるかもしれません。しかしこれは正しい形です。(補足:IUserRepository というインターフェース名自体は洗練の余地があります。)

依存性の逆転とは、言ってみればこういうことです。

  • 依存性の逆転前:UserRepository にて定義された仕様にもとづいて GreetingModel がそのコードを利用している。
  • 依存性の逆転後:GreetingModel が、自分が必要とするコードの仕様を定義し(IUserRepository)、その仕様に基づいたコードを外部に要求している。

現実の事例に例えるなら以下の通りです。

依存性を逆転する前 #

自動車メーカーと部品メーカーがいる。部品メーカーは独自に部品をつくっていて、それを自動車メーカーに渡している。

自動車メーカーは、渡された部品を使って、どういう車を作るか設計・実装する。

依存性を逆転した後 #

自動車メーカーはまず作りたい車を設計する。

その車に必要となる部品の仕様書を作成し、部品メーカーにそれを作るよう依頼する。

部品メーカーは決められた仕様に基づいて部品を作成し、自動車メーカーに納品する。

依存性を逆転すると何がいいのか #

コード上でみるとわかりづらいですが、現実の事例だとわかりやすかったのではないでしょうか。

依存性が逆転する前の場合、部品メーカーとの関係が悪くなったり、あるいは部品メーカー倒産した場合、自動車メーカーは車を作ることができなくなってしまいます。なぜならば、自動車メーカーは特定の部品メーカーに依存しているためです。

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