DP-06: Adapter パターン
既存クラスを変更せず、新しいインターフェースに適合させるパターンです。java.io.InputStreamReader が その代表例で、InputStream(バイト列)を Reader(文字列)として扱えるようにします。
Adapter パターンとは
Adapter パターンは「あるクラスのインターフェース(メソッドのシグネチャ)を、 クライアントが期待する別のインターフェースに変換する」パターンです。 電源プラグの変換アダプターのように、互換性のないものを接続する役割を担います。 特に外部ライブラリや既存の古いコード(レガシーコード)をそのまま使いながら、 新しいシステムのインターフェースに合わせるときに役立ちます。
クラスアダプター vs オブジェクトアダプター
| 項目 | クラスアダプター(継承) | オブジェクトアダプター(委譲) |
|---|---|---|
| 仕組み | Adaptee クラスを継承する | Adaptee のインスタンスを保持する |
| Java での実現 | extends で実装 | フィールドとして保持して委譲 |
| 推奨度 | Java は多重継承不可のため制限あり | Java では一般的にこちらを推奨 |
Java 標準ライブラリの代表例としてjava.io.InputStreamReaderがあります。これは InputStream(バイト列を扱う)をReader(文字列を扱う)インターフェースに適合させる Adapter です。 また Arrays.asList() も配列をList インターフェースに適合させる Adapter と見なせます。
サンプルコード
Java 8 では委譲(コンポジション)を使ったオブジェクトアダプターを実装します。既存クラス(LegacyDataReader)を一切変更せず、Adapter クラスが新旧のインターフェースをつなぎます。
よくあるミス・注意点
⚠️ 既存クラスを直接修正してしまう(開放閉鎖原則違反)
Adapter パターンの目的は「既存クラスを変更せずに新インターフェースへ対応させる」ことです。 外部ライブラリや変更してはいけないコードに手を加えてしまうと、 将来のバージョンアップ時に修正が上書きされたり、予期しない不具合を生む原因になります。 Adapter クラスを新しく作ることで、既存クラスは修正なしのまま使い続けられます。
⚠️ Adapter に業務ロジックを追加しすぎる
Adapter の役割は「インターフェースの変換」だけです。 データの加工や複雑な業務ロジックを Adapter に詰め込むと、 本来の役割が曖昧になり、テストや保守が難しくなります。 変換以外の処理は別のクラスに分けましょう。
⚠️ Arrays.asList() の戻り値に要素を追加しようとする
Arrays.asList() が返すリストは固定サイズで、add() や remove() を呼ぶとUnsupportedOperationException がスローされます。 要素を追加したい場合は new ArrayList<>(Arrays.asList(...)) でコピーしましょう。
テストする観点
- Adapter の
readData()が正しい形式([KEY=VALUE, ...])に変換されること - 元の
LegacyDataReaderクラスが変更されていないこと(fetchRawData()が元の形式を返すこと) - 入力データにカンマが含まれない場合(フィールドが1つ)でも正しく動作すること(境界値)
- 入力データが空文字の場合に空の結果が返ること(境界値)
- Adapter を Target 型として扱ったとき、ポリモーフィズムが正しく機能すること
- Java 17 版:
AdapterResultrecord のsource()とadapted()がそれぞれ正しい値を返すこと