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

Adapter:社外チケットサービスAPIとの接続変換

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

📋 この記事の目次
  1. Adapterは何のためにあるか
  2. Before:呼び出し側が変換ロジックを直接抱えている
  3. After:変換ロジックをAdapterクラスに閉じ込める
  4. こんな場面で使う/使わない
  5. まとめ
  6. 🎓 理解度テスト

TicketCraftは、社外のヘルプデスクサービス「LegacyHelpDeskApi」とも連携しています。ところが、このAPIは自社の流儀とは引数の順番も形式も異なります。

public class LegacyHelpDeskApi {
    // 引数の順番・戻り値の形式がTicketCraftの流儀と異なる社外API
    public int logIssue(int priorityLevel, String summary) {
        System.out.println("[LegacyHelpDeskApi] priority=" + priorityLevel + " summary=" + summary);
        return 12345; // 発行された社外チケット番号
    }
}

このAPIを呼び出す箇所では、TicketCraftのseverity"high"のような文字列)を、社外APIが求める数値の優先度に、呼び出しのたびに変換しています。

Adapterは何のためにあるか

Adapterは、「形の合わない2つのインターフェースの間に立ち、変換を引き受ける」 パターンです。海外製の電化製品を日本のコンセントで使うための変換プラグのようなものだと考えてください。製品側(社外API)にもコンセント側(自社コード)にも手を加えず、間に挟む1つの部品で解決します。

Before:呼び出し側が変換ロジックを直接抱えている

public class Main {
    public static void main(String[] args) {
        LegacyHelpDeskApi legacyApi = new LegacyHelpDeskApi();

        // severityの文字列を、社外APIが求める優先度の数値に、呼び出しのたびに変換している
        String severity = "high";
        int priorityLevel = severity.equals("high") ? 1 : severity.equals("medium") ? 2 : 3;
        int externalId = legacyApi.logIssue(priorityLevel, "ログイン画面が真っ白になる");

        System.out.println("社外チケット番号: " + externalId);
    }
}

実行結果:

[LegacyHelpDeskApi] priority=1 summary=ログイン画面が真っ白になる
社外チケット番号: 12345

この変換ロジック(severitypriorityLevel)は、社外APIを呼び出す場所が増えるたびにコピーされていきます。社外API側の仕様が変わったときも、変換ロジックが書かれたすべての場所を探して直す必要があります。

After:変換ロジックをAdapterクラスに閉じ込める

まず、TicketCraft自身が期待するインターフェースを定義します。

public interface TicketService {
    String registerTicket(String title, String severity);
}

社外APIとの変換を、Adapterクラスの中に1箇所だけ閉じ込めます。

public class LegacyHelpDeskApiAdapter implements TicketService {
    private final LegacyHelpDeskApi legacyApi = new LegacyHelpDeskApi();

    @Override
    public String registerTicket(String title, String severity) {
        int priorityLevel = severity.equals("high") ? 1 : severity.equals("medium") ? 2 : 3;
        int externalId = legacyApi.logIssue(priorityLevel, title);
        return "EXT-" + externalId;
    }
}
public class Main {
    public static void main(String[] args) {
        TicketService ticketService = new LegacyHelpDeskApiAdapter();
        String ticketRef = ticketService.registerTicket("ログイン画面が真っ白になる", "high");
        System.out.println("社外チケット番号: " + ticketRef);
    }
}

実行結果:

[LegacyHelpDeskApi] priority=1 summary=ログイン画面が真っ白になる
社外チケット番号: EXT-12345

呼び出し側(Main)は、TicketServiceという自社の流儀に沿ったインターフェースだけを見ればよくなりました。社外APIの引数の順番や戻り値の形式は、LegacyHelpDeskApiAdapterの中だけの知識になっています。

🧓 ベテランの現場コラム Adapterは、外部サービス連携やライブラリの乗り換えで特に威力を発揮します。将来「LegacyHelpDeskApiをやめて、別の社外サービスに乗り換える」ことになっても、新しいAdapterクラスを1つ用意してTicketServiceを実装させれば、呼び出し側のコードは一切変更せずに済みます。逆に、社外APIをそのまま自社コードのあちこちに露出させてしまうと、乗り換えのたびに広範囲の修正が必要になります。

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

  • 使う: 既存のインターフェースと外部ライブラリ・社外APIの形式が合わず、変換が必要な場合
  • 使わない: 呼び出し箇所が1箇所しかなく、将来的に変換ロジックが増える見込みもない場合。無理にAdapterクラスを作る必要はない

まとめ

  • Adapterは「形の合わない2つのインターフェースの間に立ち、変換を引き受ける」パターン
  • 変換ロジックを1箇所(Adapterクラス)に集約することで、呼び出し側から外部APIの都合を隠せる
  • 外部サービスの乗り換えにも強くなる

🎓 理解度テスト

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