第13章:手動DI(コンストラクタ注入)をやってみよう💉🧱✨
この章は「DIPを“ちゃんと動く形”にする」ための超定番スキル回だよ〜!😊 ポイントはこれ👇
- クラスの中で依存先を new しない 🚫🆕
- 代わりに、外から コンストラクタで渡す 🧺💖
- 「組み立て担当(newする場所)」を 入口(entry)に寄せる 🚪🧩
1) まず“何がイヤなの?”を1分で💥😵
❌ 悪い例:クラスの中で new しちゃう
- 上位(業務ロジック)が、下位(外部I/Oや実装詳細)をガッチリ握る🤝💦
- 支払い方法や保存先を変えると、上位コードまで巻き添えで修正が飛ぶ🌀
- テストで差し替えができなくて詰む🙈🧪
この「ガッチリ握ってる状態」をほどくのが、手動DI(コンストラクタ注入)だよ〜!💉✨
2) コンストラクタ注入ってなに?🏗️💕
依存する道具(下位)を、外から受け取る だけ!シンプル!😊
- ✅ クラスは「自分で道具を買わない」
- ✅ 「道具は入口で用意して渡す」
- ✅ だから「道具の差し替え」が一瞬でできる🔁✨
3) サンプル題材:注文処理をちょい実務っぽく📦💳
ここでは、注文を作って支払いして保存する流れにするよ😊
- 上位:OrderService(業務ルール)🧠✨
- 下位:PaymentGateway(外部決済)💳🌐
- 下位:OrderRepository(保存先)🗄️
- 下位:Clock(時間)⏰
4) ❌ DIP/DIなし:OrderService が new してる版🔥
// 下位の詳細(例)
class StripePaymentGateway {
async charge(amountYen: number): Promise<string> {
// 外部APIに通信する想定…🌐
return "pay_123";
}
}
class SqlOrderRepository {
async save(order: { id: string; paidAt: Date; paymentId: string }) {
// DBに保存する想定…🗄️
}
}
// 上位(業務ロジック)
class OrderService {
async placeOrder(amountYen: number) {
// 👇 ここが問題! 上位が下位を直newして握ってる😵
const payment = new StripePaymentGateway();
const repo = new SqlOrderRepository();
const paymentId = await payment.charge(amountYen);
const order = {
id: crypto.randomUUID(),
paidAt: new Date(),
paymentId,
};
await repo.save(order);
return order.id;
}
}
何が困る?😢
- 決済を PayPay に変えたい → OrderService を編集する羽目😵💫
- テストで「通信しない決済」を使いたい → できない🙈
- 時間(new Date)も固定できない → テストが地獄⏰🧪
6) ✅ 本題:コンストラクタ注入で「newを追い出す」💉🚪

TypeScriptは「形(構造)」で合わせられるのが強み💪
つまり interface(契約)さえ合えば差し替えOK!
// ① 決済の契約(ポート)
export interface PaymentGateway {
charge(amountYen: number): Promise<{ ok: true; paymentId: string } | { ok: false; reason: string }>;
}
// ② 保存の契約(ポート)
export interface OrderRepository {
save(order: { id: string; paidAt: Date; paymentId: string }): Promise<void>;
}
// ③ 時間の契約(ポート)
export interface Clock {
now(): Date;
}
6) ✅ 本題:コンストラクタ注入で「newを追い出す」💉🚪
export class OrderService {
// 👇 依存をコンストラクタで受け取る(これがコンストラクタ注入)
constructor(
private readonly payment: PaymentGateway,
private readonly repo: OrderRepository,
private readonly clock: Clock,
) {}
async placeOrder(amountYen: number) {
const charged = await this.payment.charge(amountYen);
if (!charged.ok) {
throw new Error(`支払い失敗: ${charged.reason}`);
}
const order = {
id: crypto.randomUUID(),
paidAt: this.clock.now(),
paymentId: charged.paymentId,
};
await this.repo.save(order);
return order.id;
}
}
ここが最高ポイント🎉✨
- OrderService は「決済がStripeかどうか」知らない🙈
- OrderService は「DBがSQLかどうか」知らない🙈
- OrderService は「時間が本物か固定か」知らない🙈 → だから差し替え・テストが最強になる💪🧪
7) 実装(下位)は“契約を満たすだけ”でOK🔧🌐🗄️
export class StripePaymentGateway implements PaymentGateway {
async charge(amountYen: number) {
// ここで外部決済APIに通信する想定
return { ok: true as const, paymentId: "pay_123" };
}
}
export class SqlOrderRepository implements OrderRepository {
async save(order: { id: string; paidAt: Date; paymentId: string }) {
// ここでDB保存する想定
}
}
export class SystemClock implements Clock {
now() {
return new Date();
}
}
8) “組み立て場所”はどこが良い?👉 入口(entry)だよ🚪✨
ここが超大事! new していいのは、基本的に入口だけ(組み立て担当)👏
import { OrderService } from "./OrderService";
import { StripePaymentGateway } from "./StripePaymentGateway";
import { SqlOrderRepository } from "./SqlOrderRepository";
import { SystemClock } from "./SystemClock";
async function main() {
// 👇 入口で組み立て(ここはnewしてOK!)
const payment = new StripePaymentGateway();
const repo = new SqlOrderRepository();
const clock = new SystemClock();
const service = new OrderService(payment, repo, clock);
const orderId = await service.placeOrder(1200);
console.log("注文完了:", orderId);
}
main().catch(console.error);
この「入口で組み立てる場所」をよく Composition Root(コンポジションルート) って呼ぶよ🌱✨ (言葉は難しいけど、やってることは“組み立て係”ね😊)
9) よくあるミスあるある😵💫⚠️
ミス①:途中でまた new しちゃう
- 「ちょっとだけなら…」が増殖するやつ🥲
- 迷ったら「それは入口で作れない?」って考えるのがコツ💡
ミス②:依存が増えすぎてコンストラクタが長い
-
あるある!でも焦らないでOK😊
-
対策は次のどれか👇
- 依存が多い原因(責務過多)を疑う🧠
- 近いものをまとめた“小さな依存の塊”を作る🧺
- 「設定値」は ConfigProvider 的にまとめる⚙️
10) TypeScript/周辺の“いま”メモ(2026/01時点)🗓️✨
- TypeScriptの安定版は 5.8.3 が確認できて、5.9はRC/プレリリース情報も出てるよ📌 (GitHub)
- Node.jsは v24 がActive LTSとして案内されてる(開発・運用で選ばれやすいライン)🟢 (Node.js)
- VS Codeのリリースノートも 2026年1月の更新が出てるよ📝✨ (Visual Studio Code)
(この章の内容自体はバージョンが変わっても強い基本だよ〜💪😊)
章末セット🍀📌
まとめ(3行)🧾✨
- クラスの中で new すると、依存が固定されて差し替えできなくなる😵
- コンストラクタ注入は「外から依存を渡す」だけの最強テク💉
- new は入口(entry)に寄せて、業務ロジックをキレイに保つ💖
ミニ演習(1〜2問)✍️🧸
- さっきの「DIP/DIなしコード」を、PaymentGateway / OrderRepository / Clock を使う形に書き換えてみてね😊
- 入口(main)で “Stripe→FakePayment” に差し替えても OrderService を一切変更しないで動く状態にしてみてね🔁✨
AIに聞く用プロンプト例🤖💬
- 「このTypeScriptコードのクラス内でnewしている依存を、コンストラクタ注入にリファクタして。入口で組み立てるmainも一緒に提案して」
- 「依存が増えてコンストラクタが長い。責務分割の観点で、どこが“やりすぎ”か指摘して、改善案を2パターン出して」
- 「Fake実装(通信しないPaymentGateway)を作って、OrderServiceのテストがしやすい形にして。差し替えの例も見せて」
次の第14章(HTTP/外部APIクライアント)に行くと、この“入口で組み立てる”がさらに気持ちよく効いてくるよ〜🌐📡✨