概要
通知機能を設計するとき、「緊急通知 x メール送信」「定期レポート x SMS 送信」のようにクラスの組み合わせが掛け算で増えていく問題に直面することがあります。Bridge パターンは、「抽象(通知の種類)」と「実装(送信手段)」を独立した階層に分離し、合成で組み合わせることでクラス爆発を防ぎます。新しい通知の種類を追加しても送信手段のコードには触れず、新しい送信手段を追加しても通知ロジックは変わりません。この記事では通知システムを題材に Bridge パターンの構造を示し、Java 17 の record・sealed interface を活用した簡潔な実装と、Adapter パターンとの違いを整理します。
使いどころ
通知の種類(緊急・定期・承認依頼)と送信チャネル(メール・SMS・Slack)を独立に追加・組み合わせできるようにする
帳票のフォーマット(一覧・明細・集計)と出力先(PDF・Excel・画面表示)を分離し、どちらも単独で拡張可能にする
ログの種別(監査・アクセス・エラー)と出力先(ファイル・DB・外部サービス)を独立に管理する
コード例
public class BridgePatternSample {
// 実装インターフェース(Implementor)
interface MessageSender {
void send(String to, String subject, String body);
}
// 具体実装(Java 17: record で簡潔に)
record EmailSender(String smtpHost) implements MessageSender {
@Override
public void send(String to, String subject, String body) {
System.out.println("[メール] SMTP: " + smtpHost);
System.out.println(" 宛先: " + to + " / 件名: " + subject);
}
}
record SmsSender(String gateway) implements MessageSender {
@Override
public void send(String to, String subject, String body) {
System.out.println("[SMS] GW: " + gateway);
System.out.println(" 宛先: " + to + " / 内容: " + body);
}
}
// 抽象クラス(Abstraction)
static abstract class Notification {
protected final MessageSender sender;
Notification(MessageSender sender) { this.sender = sender; }
abstract void notify(String recipient, String message);
}
// 具体抽象
static class UrgentNotification extends Notification {
UrgentNotification(MessageSender sender) { super(sender); }
@Override
void notify(String recipient, String message) {
sender.send(recipient, "【緊急】" + message,
"至急確認: " + message);
}
}
static class ReportNotification extends Notification {
ReportNotification(MessageSender sender) { super(sender); }
@Override
void notify(String recipient, String message) {
sender.send(recipient, "定期レポート", message);
}
}
public static void main(String[] args) {
// 緊急 x メール
var urgentEmail = new UrgentNotification(
new EmailSender("smtp.example.com"));
urgentEmail.notify("[email protected]", "サーバー障害");
System.out.println();
// 定期レポート x SMS
var reportSms = new ReportNotification(
new SmsSender("sms-gw.example.com"));
reportSms.notify("090-1234-5678", "売上: 100万円");
}
}Version Coverage
record で実装クラスを簡潔に定義できる。sealed interface で送信手段の種別を型安全に限定することも可能。
// Java 17: record で実装を簡潔に
record EmailSender(String smtpHost) implements MessageSender {
@Override
public void send(String to, String subj, String body) {
System.out.println("[メール] " + smtpHost + " → " + to);
}
}
var n = new UrgentNotification(new EmailSender("smtp.example.com"));Library Comparison
注意点
Bridge パターンは抽象と実装の両方が独立に拡張される場合に有効。片方だけが変わるなら Strategy パターンで十分な場合が多い
抽象クラスが実装インターフェースへの参照を持つ構造が Bridge の核心。単なるインターフェース分離と混同しないよう注意
設計初期から Bridge を適用すると過剰設計になりやすい。まず Strategy で始めて、抽象側にも階層が必要になった時点で Bridge に移行するのが現実的
FAQ
Strategy は振る舞いの切り替えに焦点を当て、実装側だけが変わります。Bridge は抽象と実装の両方が独立に拡張される構造です。
Adapter は既存のインターフェースを変換するのが目的です。Bridge は設計段階から抽象と実装を分離し、将来の拡張に備えます。
通知、帳票出力、ロギングなど、種類と手段の組み合わせが増える場面で有効です。JDBC のドライバー構造も Bridge パターンの実例です。