Javaを鍛える|デザインパターン全23回 #8

Bridge:出力形式とチケット種別の分離

  • Java
  • デザインパターン
  • Bridge
  • Javaを鍛える

📋 この記事の目次
  1. Bridgeは何のためにあるか
  2. Before:種類と出力形式が1つのクラスに固まっている
  3. After:出力形式を切り離し、組み合わせて使う
  4. こんな場面で使う/使わない
  5. まとめ
  6. 🎓 理解度テスト

TicketCraftには、チケットの「概要レポート」「詳細レポート」を、画面表示・PDF出力など複数の形式で出せる機能があります。今のコードは、種類と出力形式の組み合わせごとにクラスが1つずつ用意されています。

public class SummaryScreenReport {
    public void show(String title) {
        System.out.println("[画面] 概要: " + title);
    }
}

public class DetailPdfReport {
    public void show(String title, String detail) {
        System.out.println("[PDF] 詳細: " + title + " - " + detail);
    }
}

これに加えてSummaryPdfReportDetailScreenReportもあり、すでに4クラスです。ここに「Slack出力」を追加すると6クラス、将来「担当者別レポート」のような3つ目の種類を追加すると9クラスと、組み合わせの数だけクラスが掛け算式に増えていきます

Bridgeは何のためにあるか

Bridgeは、「何を(レポートの種類)」と「どう出すか(出力形式)」という2つの軸を分離し、それぞれ独立に増やせるようにする」 パターンです。洋服の「デザイン」と「サイズ」を別々の軸で管理すれば、デザイン×サイズの組み合わせごとに服を1着ずつ縫い直す必要がなくなるのと同じ発想です。

Before:種類と出力形式が1つのクラスに固まっている

public class Main {
    public static void main(String[] args) {
        SummaryScreenReport screenSummary = new SummaryScreenReport();
        screenSummary.show("ログイン画面が真っ白になる");

        DetailPdfReport pdfDetail = new DetailPdfReport();
        pdfDetail.show("ログイン画面が真っ白になる", "Chromeのバージョン120で再現");
    }
}

実行結果:

[画面] 概要: ログイン画面が真っ白になる
[PDF] 詳細: ログイン画面が真っ白になる - Chromeのバージョン120で再現

動作はしていますが、新しい出力形式(Slack)を追加するには、SummarySlackReportDetailSlackReportという2つの新規クラスが必要です。

After:出力形式を切り離し、組み合わせて使う

出力形式(実装側)を、共通のインターフェースとして切り出します。

public interface ReportFormatter {
    void output(String content);
}

public class ScreenFormatter implements ReportFormatter {
    @Override
    public void output(String content) {
        System.out.println("[画面] " + content);
    }
}

public class PdfFormatter implements ReportFormatter {
    @Override
    public void output(String content) {
        System.out.println("[PDF] " + content);
    }
}

public class SlackFormatter implements ReportFormatter {
    @Override
    public void output(String content) {
        System.out.println("[Slack] " + content);
    }
}

レポートの種類(抽象側)は、出力形式を「橋(Bridge)」として保持するだけにします。

public abstract class TicketReport {
    protected final ReportFormatter formatter;

    protected TicketReport(ReportFormatter formatter) {
        this.formatter = formatter;
    }

    public abstract void show(String title, String detail);
}

public class SummaryReport extends TicketReport {
    public SummaryReport(ReportFormatter formatter) {
        super(formatter);
    }

    @Override
    public void show(String title, String detail) {
        formatter.output("概要: " + title);
    }
}

public class DetailReport extends TicketReport {
    public DetailReport(ReportFormatter formatter) {
        super(formatter);
    }

    @Override
    public void show(String title, String detail) {
        formatter.output("詳細: " + title + " - " + detail);
    }
}
public class Main {
    public static void main(String[] args) {
        TicketReport screenSummary = new SummaryReport(new ScreenFormatter());
        screenSummary.show("ログイン画面が真っ白になる", "");

        TicketReport pdfDetail = new DetailReport(new PdfFormatter());
        pdfDetail.show("ログイン画面が真っ白になる", "Chromeのバージョン120で再現");

        // Slackという新しい出力形式を追加しても、新しいクラスは1つだけでよい
        TicketReport slackSummary = new SummaryReport(new SlackFormatter());
        slackSummary.show("ダークモードが欲しい", "");
    }
}

実行結果:

[画面] 概要: ログイン画面が真っ白になる
[PDF] 詳細: ログイン画面が真っ白になる - Chromeのバージョン120で再現
[Slack] 概要: ダークモードが欲しい

「レポートの種類」(SummaryReportDetailReport)と「出力形式」(ScreenFormatterPdfFormatterSlackFormatter)が、それぞれ独立に増やせるクラスになりました。種類が2つ、形式が3つでも、必要なクラスは2+3=5つで済みます(組み合わせの数である2×3=6を用意する必要はありません)。

🧓 ベテランの現場コラム Bridgeは「継承ではなく、オブジェクトを持たせて委譲する」という考え方の代表例です。「このクラス、似たようなクラスが何個もあるな」と感じたら、その違いが実は2つの軸(今回で言えば種類と出力形式)に分解できないか考えてみましょう。ただし、軸が最初から1つしかなく、今後も増える見込みがない場合にまでBridge化すると、かえって間接参照が増えて読みにくくなることもあります。

こんな場面で使う/使わない

  • 使う: 「何をするか」と「どう実現するか」という2つの軸があり、両方が今後増える可能性がある場合
  • 使わない: 軸が1つしかなく、組み合わせの爆発が起きていない場合

まとめ

  • Bridgeは「何を」と「どう出すか」の2つの軸を分離し、独立に拡張できるようにするパターン
  • 組み合わせの数だけクラスが増える「クラスの爆発」を防げる
  • 軸が実際に複数あり、両方が今後も増える見込みがある場合に検討する

🎓 理解度テスト

学んだ内容を確認しましょう。全3問。