Q. そもそもDIってなに?
A. DI(Dependency Injection:依存性注入)とは、コンポーネントやクラスが外部からその必要な依存関係(他のクラスやサービスなど)を受け取る設計パターンです。
Q. わかりづらいから童話調で説明して?
A.
むかしむかし、あるプログラムの王国がありました。
『依存の王国のものがたり』
ある日、ユーザーサービス王子は、データベース姫と仲良く暮らしていました。
// むかしのコード
class ユーザーサービス王子 {
private データベース姫 = new データベース姫(); // 強い結びつき
ユーザーを探す(id: string) {
return this.データベース姫.探す(id);
}
}
でも、これには大きな問題がありました。ユーザーサービス王子は、データベース姫なしでは何もできなかったのです。テストの妖精が来ても、データベース姫を置き換えることができず、とても困っていました。
そこで、賢者のDI(依存性の注入)さんが現れ、こう言いました。
「これからは、王子様とお姫様は、もっと自由な関係になりましょう。お互いを直接知らなくても、誰かが引き合わせてくれれば良いのです」
そして、賢者のDIさんは、こんな魔法をかけました:
// 新しい魔法のコード
interface データベースの約束 {
探す(id: string): Promise<any>;
}
class ユーザーサービス王子 {
// 誰でも良いから、約束を守れる人を待つ
constructor(private データベース: データベースの約束) {}
ユーザーを探す(id: string) {
return this.データベース.探す(id);
}
}
class データベース姫 implements データベースの約束 {
探す(id: string) {
return Promise.resolve({ id, name: 'はなこ' });
}
}
// テストの妖精さん
class テストデータベース implements データベースの約束 {
探す(id: string) {
return Promise.resolve({ id, name: 'てすとさん' });
}
}
そして、王国の人々は、こんな風に暮らすようになりました:
// 普段の暮らし
const データベース = new データベース姫();
const 王子様 = new ユーザーサービス王子(データベース);
// テストの日
const テストデータ = new テストデータベース();
const テスト用の王子様 = new ユーザーサービス王子(テストデータ);
この魔法のおかげで:
王子様は、誰が来ても対応できるようになりました
テストの妖精さんも、簡単にテストができるようになりました
みんなが自由に、でも約束を守って暮らせるようになりました
DIの魔法によって、プログラムの王国はより柔軟で、テストしやすく、幸せな場所になりました。
めでたし、めでたし。
おまけ:王国の新しい規則
// 王国のDIコンテナ
class 王国のルール {
private お供たち = new Map();
仲間を増やす(名前: string, 仲間: any) {
this.お供たち.set(名前, 仲間);
}
仲間を呼ぶ(名前: string) {
return this.お供たち.get(名前);
}
}
const 王国 = new 王国のルール();
王国.仲間を増やす('データベース', new データベース姫());
王国.仲間を増やす('ユーザーサービス',
new ユーザーサービス王子(王国.仲間を呼ぶ('データベース')));
こうして、王国の皆は、お互いを思いやりながら、でも自由に暮らせるようになりました。これこそが、DIの魔法がもたらした幸せなのです。
Q. 「これからは、王子様とお姫様は、もっと自由な関係になりましょう。お互いを直接知らなくても、誰かが引き合わせてくれれば良いのです」という言葉の意味がわからない
A.
この部分を、より具体的に説明しましょう。
昔の関係(DIを使わない場合):
// 昔の王子様は、必ずデータベース姫と一緒じゃないといけなかった
class ユーザーサービス王子 {
// 王子様が直接データベース姫を呼び出している(強い結びつき)
private データベース姫 = new データベース姫();
ユーザーを探す() {
// データベース姫しか選べない!
return this.データベース姫.探す();
}
}
// 使う時
const 王子様 = new ユーザーサービス王子();
// データベース姫以外を使うことができない!
これは、まるで王子様がデータベース姫としか結婚できないような状態です。テストをしたいときも、他のデータを使いたいときも、必ずデータベース姫を使わないといけません。
新しい関係(DIを使う場合):
// 約束(インターフェース)を作る
interface データベースの約束 {
探す(): Promise<any>;
}
class ユーザーサービス王子 {
// 王子様は「データベースの約束」を守れる人なら誰でもOK!
constructor(private データベース: データベースの約束) {}
ユーザーを探す() {
// 渡されたデータベースを使う(柔軟な関係)
return this.データベース.探す();
}
}
// 本番のデータベース姫
class データベース姫 implements データベースの約束 {
探す() {
return Promise.resolve({ name: 'はなこ' });
}
}
// テスト用のデータベース妖精
class テストデータベース implements データベースの約束 {
探す() {
return Promise.resolve({ name: 'てすとさん' });
}
}
// 使う時
// 本番では
const 本番用の相手 = new データベース姫();
const 王子様1 = new ユーザーサービス王子(本番用の相手);
// テストでは
const テスト用の相手 = new テストデータベース();
const 王子様2 = new ユーザーサービス王子(テスト用の相手);
つまり:
1.昔は王子様とデータベース姫が強く結びついていて離れられなかった
2.新しい方法では、「データベースの約束」を守れる人なら誰でも王子様と協力できる
3.誰かが王子様に「この人と一緒に仕事してね」と紹介する(これが依存性の注入)
実際の例で言うと:
// 昔の方法(固定的な関係)
class レストラン {
private コック = new 日本料理コック(); // 日本料理しか作れない!
}
// 新しい方法(柔軟な関係)
interface 料理を作れる {
料理を作る(): string;
}
class レストラン {
constructor(private コック: 料理を作れる) {} // どんなコックでもOK!
}
// いろんなコックを使える
const 和食コック = new 日本料理コック();
const 中華コック = new 中華料理コック();
const 和食レストラン = new レストラン(和食コック);
const 中華レストラン = new レストラン(中華コック);
このように、DIを使うことで:
1.より柔軟な関係が作れる
2.テストが簡単になる
3.コードの再利用がしやすくなる
という利点があります。これが「自由な関係」の意味です
なんとなくわかったこと
DIは、クラスやコンポーネントが必要なものを自分で作るのではなく、外から受け取る方式である。
これにより、コードの部品を簡単に付け替えられるようになり(テストがしやすくなり)、柔軟性が高まる。
たとえば、レストランクラスが特定のコックを直接作るのではなく、外から任意のコックを受け取れるようにする、というイメージ。
結論
最後のコックの例えが一番わかりやすかったな……