第40章:Bridge ② TypeScript定番:interface実装を注入(DIっぽく)💉
ねらい🎯
- 「やりたいこと(抽象)」と「どうやって送るか(実装)」を分けて、組合せ爆発💥を止める
- **interface(または type)を“注入”**して、差し替えを自然にできるようになる
- テストがラク🧪(送信部分をスタブに差し替えられる)
まず困りごと😵:2軸が増えるとクラス地獄
たとえば通知で…
- 軸A:何を通知?(注文確定 / キャンペーン / エラーなど)
- 軸B:どう送る?(メール / アプリ内 / SMS / Slackなど)
これを「継承だけ」でやると、A×B の数だけクラスが増えがち…😇 Bridge は、ここをスッキリ分けます✨
Bridge は「抽象(abstraction)と実装(implementation)を分離して、両方を独立に変えられる」ようにする構造パターンです。(リファクタリング Guru)
Bridgeの考え方🧠✨(図でイメージ)
- 抽象側(Abstraction):通知の“意味”を知ってる(例:注文確定を通知する)
- 実装側(Implementor):送信の“やり方”を知ってる(例:メールで送る)
つまり…
- 抽象は「通知したい内容」を作る📝
- 実装は「それをどう送るか」を担当📮
- 抽象は実装を コンストラクタで受け取る(注入) 💉

1) 最小の完成形🧁(interface注入でBridge)
ポイントはこれ👇
Sender(実装側)を interface にするNotifier(抽象側)が Senderを受け取って使う
// sender.ts(実装側の“共通口”)
export type SendPayload = {
to: string;
subject: string;
body: string;
};
export interface Sender {
send(payload: SendPayload): Promise<void>;
}
// notifier.ts(抽象側)
import type { Sender, SendPayload } from "./sender.js";
export abstract class Notifier {
constructor(protected readonly sender: Sender) {}
// 抽象側は「何を通知するか」を決める
abstract notify(input: unknown): Promise<void>;
protected async deliver(payload: SendPayload) {
await this.sender.send(payload);
}
}
// order-notifier.ts(抽象側の具体例:注文確定通知)
import { Notifier } from "./notifier.js";
type Order = {
id: string;
customerEmail: string;
totalYen: number;
};
export class OrderConfirmedNotifier extends Notifier {
async notify(input: unknown): Promise<void> {
const order = input as Order; // 学習用にシンプル化(実務は型ガード推奨)
await this.deliver({
to: order.customerEmail,
subject: `ご注文ありがとうございます(注文ID: ${order.id})`,
body: `合計: ${order.totalYen}円です☕️ またのご利用お待ちしてます!`,
});
}
}
// senders.ts(実装側の具体例:2種類だけ用意)
import type { Sender, SendPayload } from "./sender.js";
export class ConsoleSender implements Sender {
async send(payload: SendPayload): Promise<void> {
console.log("📮 ConsoleSender send:", payload);
}
}
export class FakeMailSender implements Sender {
async send(payload: SendPayload): Promise<void> {
// ここでは“メール送信したフリ”だけ(学習用)
console.log("✉️ FakeMailSender send:", payload.to, payload.subject);
}
}
// demo.ts(組み合わせ自由!)
import { OrderConfirmedNotifier } from "./order-notifier.js";
import { ConsoleSender, FakeMailSender } from "./senders.js";
const order = { id: "A-100", customerEmail: "alice@example.com", totalYen: 1280 };
// 同じ通知(抽象)を、送信手段(実装)だけ差し替え✨
await new OrderConfirmedNotifier(new ConsoleSender()).notify(order);
await new OrderConfirmedNotifier(new FakeMailSender()).notify(order);
✅ これで「通知の種類(抽象)」を増やしても、送信手段(実装)に引っ張られない ✅ 送信手段(実装)を増やしても、通知(抽象)側は変えずに済む → 組合せ爆発が止まる🎉
2) TypeScriptらしさ🧡:クラスを増やさず“オブジェクト注入”でもOK
Sender は class じゃなくて オブジェクト1個でも実装できます(TSの構造的型付けが効く✨)
import type { Sender } from "./sender.js";
import { OrderConfirmedNotifier } from "./order-notifier.js";
// satisfies:型に合ってるかだけチェックして、推論は保つ✨
const spySender = {
sent: [] as any[],
async send(payload: any) {
this.sent.push(payload);
},
} satisfies Sender;
satisfies は「型に合うか検査しつつ、値の具体的な型情報は保つ」ための演算子です。(TypeScript)
3) テストがラクになる🧪(送信だけ差し替える)
ここでは 標準のテストランナー(node:test) を使う例にします✨(追加ライブラリなし)
Nodeには組み込みのテストランナーがあります。(Node.js)
// order-notifier.test.ts
import test from "node:test";
import assert from "node:assert/strict";
import type { Sender, SendPayload } from "./sender.js";
import { OrderConfirmedNotifier } from "./order-notifier.js";
test("OrderConfirmedNotifier: 注文確定の文面が作れる", async () => {
const sent: SendPayload[] = [];
const sender: Sender = {
async send(payload) {
sent.push(payload);
},
};
const notifier = new OrderConfirmedNotifier(sender);
await notifier.notify({ id: "A-100", customerEmail: "alice@example.com", totalYen: 1280 });
assert.equal(sent.length, 1);
assert.equal(sent[0].to, "alice@example.com");
assert.match(sent[0].subject, /A-100/);
assert.match(sent[0].body, /1280円/);
});
💡ここが気持ちいい所
- テストでは「送信」そのものは不要🙅♀️
senderを差し替えるだけで、通知ロジックだけを確認できる✅
4) ハンズオン🛠️(今日の課題)
課題A:送信手段を1個増やす📮➕
InAppSenderを追加してみよう(例:consoleに📱をつけて出すだけでOK)OrderConfirmedNotifierをそのまま使って動く?(動いたら勝ち🎉)
課題B:通知の種類を1個増やす📝➕
PromoNotifier(キャンペーン通知)を追加- 送信は既存の
ConsoleSenderを使い回しでOK✨
課題C:小さく設計を良くする💄
notify(input: unknown)のasをやめて、簡単な型ガードにしてみる(Adapter章の復習にもなるよ🧼)
5) ありがちなミス&回避💡
-
「抽象が分厚すぎ」問題:
Notifierに業務判断を詰め込みすぎると逆に読みにくい😵💫 → 抽象は“薄く”、差分がある所だけ持つのがコツ✨ -
「BridgeじゃなくStrategyでよくない?」問題:
- 1軸だけ差し替えたい → Strategy寄り
- 2軸が増えて組合せ爆発 → Bridgeが刺さる🎯
-
「抽象と実装の境界が曖昧」問題: → “通知文を作る”は抽象、 “送る”は実装、と線引きを固定しよう🧠
6) AIプロンプト例🤖💬(コピペOK)
BridgeパターンをTypeScriptで学習中です。
「通知の種類(注文確定/キャンペーン)」と「送信手段(メール/アプリ内)」の2軸が増えて組合せ爆発しそうです。
制約:
- 余計な独自クラスを増やさず、interface注入(DIっぽく)で最小構成
- Senderはinterface(またはtype)で、Notifierが受け取る形
- 1) 最小実装 2) テスト(node:test) 3) よくある失敗 を出して
題材:カフェ注文
7) ミニ最新メモ🗞️✨(2026目線)
- TypeScriptは npm の latest が 5.9系(例:5.9.3)になっています。(npmjs.com)
- Node.jsには 組み込みテストランナーがあります(
node:test)。(Node.js)
(ちょい安全🍀)npmは“似た名前”の悪意パッケージもたまにあるので、入れる時はパッケージ名をよく見るクセが安心です🧯(TechRadar)