概要

通知機能を設計するとき、「緊急通知 x メール送信」「定期レポート x SMS 送信」のようにクラスの組み合わせが掛け算で増えていく問題に直面することがあります。Bridge パターンは、「抽象(通知の種類)」と「実装(送信手段)」を独立した階層に分離し、合成で組み合わせることでクラス爆発を防ぎます。新しい通知の種類を追加しても送信手段のコードには触れず、新しい送信手段を追加しても通知ロジックは変わりません。この記事では通知システムを題材に Bridge パターンの構造を示し、Java 17 の record・sealed interface を活用した簡潔な実装と、Adapter パターンとの違いを整理します。

使いどころ

通知の種類(緊急・定期・承認依頼)と送信チャネル(メール・SMS・Slack)を独立に追加・組み合わせできるようにする

帳票のフォーマット(一覧・明細・集計)と出力先(PDF・Excel・画面表示)を分離し、どちらも単独で拡張可能にする

ログの種別(監査・アクセス・エラー)と出力先(ファイル・DB・外部サービス)を独立に管理する

コード例

BridgePatternSample.java
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万円");
    }
}

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

record で実装クラスを簡潔に定義できる。sealed interface で送信手段の種別を型安全に限定することも可能。

Java 17
// 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

標準 API(abstract class + interface)抽象と実装の両方に拡張の余地があり、組み合わせの自由度を確保したいとき。設計の意図がコードから読み取りにくい場合がある。チーム内で設計図を共有しておくこと。
JDBC ドライバー構造Java 標準の Bridge パターン適用例として理解しておくと、自前の設計判断に役立つ。JDBC は大規模な API なので、Bridge の学習目的には通知システムのような小さな題材のほうが理解しやすい。

注意点

Bridge パターンは抽象と実装の両方が独立に拡張される場合に有効。片方だけが変わるなら Strategy パターンで十分な場合が多い

抽象クラスが実装インターフェースへの参照を持つ構造が Bridge の核心。単なるインターフェース分離と混同しないよう注意

設計初期から Bridge を適用すると過剰設計になりやすい。まず Strategy で始めて、抽象側にも階層が必要になった時点で Bridge に移行するのが現実的

FAQ

Bridge パターンと Strategy パターンの違いは何ですか。

Strategy は振る舞いの切り替えに焦点を当て、実装側だけが変わります。Bridge は抽象と実装の両方が独立に拡張される構造です。

Bridge パターンと Adapter パターンの違いは何ですか。

Adapter は既存のインターフェースを変換するのが目的です。Bridge は設計段階から抽象と実装を分離し、将来の拡張に備えます。

Bridge パターンは実務でどのくらい使いますか。

通知、帳票出力、ロギングなど、種類と手段の組み合わせが増える場面で有効です。JDBC のドライバー構造も Bridge パターンの実例です。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます