アダプターパターン
CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 71/87。
アダプターパターン(Adapter Pattern)は、互換性のないインターフェースを連携させるための構造に関するデザインパターンです。これは2つのクラス間の架け橋として機能し、あるクラスのインターフェースをクライアントが期待するインターフェースに変換します。欧州のデバイスをアメリカのコンセントに差し込めるようにする電源アダプターのようなものだと考えてください。
オーディオファイルを再生する既存の MediaPlayer インターフェースがあると想像してください。しかし、ビデオを再生するために、まったく異なるインターフェースを使用するサードパーティライブラリを統合する必要があります:
// コードが使用している既存のインターフェース
interface MediaPlayer {
void play(String filename);
}
// 互換性のないインターフェースを持つサードパーティのクラス
class AdvancedVideoPlayer {
public void playVideo(String filename) {
System.out.println("Playing video: " + filename);
}
}アダプターは互換性のないクラスをラップし、期待されるインターフェースを実装して、それらの間の呼び出しを変換します:
class VideoPlayerAdapter implements MediaPlayer {
private AdvancedVideoPlayer videoPlayer;
public VideoPlayerAdapter() {
this.videoPlayer = new AdvancedVideoPlayer();
}
public void play(String filename) {
videoPlayer.playVideo(filename);
}
}これで、クライアントコードは使い慣れた MediaPlayer インターフェースを介してビデオプレーヤーを使用できるようになります。
MediaPlayer player = new VideoPlayerAdapter();
player.play("movie.mp4"); // 出力: ビデオを再生中: movie.mp4アダプターパターンは、ソースコードを変更することなくレガシーコードやサードパーティライブラリを統合する際に非常に価値があります。クライアントは、適応されたクラスを操作していることを意識することなく、単に自分が知っているインターフェースを使用します。
チャレンジ
簡単アダプターパターン(Adapter Pattern)を使用して、温度変換システムを構築しましょう!既存の気象アプリケーションは摂氏(Celsius)で動作しますが、華氏(Fahrenheit)でしか値を出力できないレガシーな温度センサーを統合する必要があります。どちらのシステムも変更することなく、そのギャップを埋めるアダプターを作成します。
コードは以下の4つのファイルに分けて構成します:
TemperatureProvider.java: 気象アプリケーションが期待するインターフェースを定義します。摂氏の温度を double 型で返すgetTemperatureCelsius()メソッドを宣言してください。FahrenheitSensor.java: 互換性のないインターフェースを持つレガシーセンサーを表します。華氏の温度を保持する private な double 型のフィールドを持ち、コンストラクタを通じて設定されるクラスを作成してください。保持している華氏の値を返すreadFahrenheit()メソッドを実装してください。SensorAdapter.java: 華氏センサーを気象アプリケーションと互換性を持たせるためのアダプターを作成します。このアダプターはTemperatureProviderを実装し、FahrenheitSensorをラップ(保持)する必要があります。getTemperatureCelsius()が呼び出されたとき、センサーから値を読み取り、次の公式を使用して摂氏に変換して返します:(fahrenheit - 32) * 5 / 9。Main.java: すべてを統合します!1つの入力として、華氏の温度(double)を受け取ります。入力値を使用して
FahrenheitSensorを作成します。次に、このセンサーをラップするSensorAdapterを作成します。気象アプリケーションが期待されるインターフェースを通じて利用できることを示すために、アダプターをTemperatureProvider型の変数に格納してください。結果を次の形式で出力してください:
"Temperature: " + String.format("%.1f", celsius) + " C"(celsius は小数点第1位までフォーマットされた値です)。
入力として、華氏の温度(double)を1つ受け取ります。
例えば、入力が 98.6 の場合、出力は以下のようになります:
Temperature: 37.0 CMain クラスが完全に TemperatureProvider インターフェースを通じて動作していることに注目してください。実際の温度が、舞台裏で適応された華氏センサーから来ていることを Main クラスは関知していません。これがアダプターパターンの洗練された点です!
チートシート
アダプターパターン(Adapter Pattern)は、2つのクラス間の架け橋として機能し、一方のインターフェースをクライアントが期待する別のインターフェースに変換することで、互換性のないインターフェース同士を連携させる構造に関するデザインパターンです。
主要な構成要素
- Target Interface(ターゲットインターフェース): クライアントが使用することを期待しているインターフェース
- Adaptee(アダプティ): 互換性のないインターフェースを持つ既存のクラス
- Adapter(アダプター): ターゲットインターフェースを実装し、アダプティをラップして、それらの間の呼び出しを変換するクラス
基本構造
// ターゲットインターフェース
interface MediaPlayer {
void play(String filename);
}
// アダプティ(互換性のないクラス)
class AdvancedVideoPlayer {
public void playVideo(String filename) {
System.out.println("Playing video: " + filename);
}
}
// アダプター
class VideoPlayerAdapter implements MediaPlayer {
private AdvancedVideoPlayer videoPlayer;
public VideoPlayerAdapter() {
this.videoPlayer = new AdvancedVideoPlayer();
}
public void play(String filename) {
videoPlayer.playVideo(filename);
}
}
// クライアントでの使用
MediaPlayer player = new VideoPlayerAdapter();
player.play("movie.mp4"); // 出力: Playing video: movie.mp4いつ使用するか
アダプターパターンは、以下のような場合に役立ちます:
- ソースコードを変更せずに、レガシーコードやサードパーティライブラリを統合する場合
- 互換性のないインターフェースを連携させる場合
- クライアントがアダプトされた実装を意識せずに済むようにする必要がある場合
自分で試してみよう
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
double fahrenheit = scanner.nextDouble();
// TODO: 入力値を使用して FahrenheitSensor を作成する
// TODO: センサーをラップする SensorAdapter を作成する
// TODO: アダプターを TemperatureProvider 変数に格納する
// TODO: 摂氏(Celsius)で温度を取得し、それを表示する
// 形式: 小数点以下1桁で "Temperature: [celsius] C"
// ヒント: フォーマットには String.format("%.1f", value) を使用する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4継承
継承の基本 (extends)super キーワードメソッドのオーバーライド (@Override)コンストラクタチェーンObject クラス単一継承と多段階継承なぜ多重継承ができないのかまとめ:従業員の階層構造