アダプターパターン
CoddyのC#ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 57/70。
アダプターパターン(Adapter pattern)は、互換性のないインターフェースを連携させるための構造に関するパターンです。これは2つのクラス間の架け橋として機能し、あるクラスのインターフェースをクライアントが期待するインターフェースに変換します。ヨーロッパのデバイスをアメリカのコンセントに差し込めるようにする電源アダプターのようなものだと考えてください。
このパターンは、既存のクラスを使用したいが、そのインターフェースがコードの必要とするものと一致しない場合に特に便利です。元のクラス(制御できない場合があります)を修正する代わりに、それをラップするアダプターを作成します:
// 互換性のないインターフェースを持つ既存のクラス
public class OldPrinter
{
public void PrintMessage(string msg) => Console.WriteLine($"Old: {msg}");
}
// コードが期待するインターフェース
public interface IPrinter
{
void Print(string text);
}
// アダプターがギャップを埋める
public class PrinterAdapter : IPrinter
{
private OldPrinter _oldPrinter;
public PrinterAdapter(OldPrinter oldPrinter)
{
_oldPrinter = oldPrinter;
}
public void Print(string text)
{
_oldPrinter.PrintMessage(text); // 古いインターフェースに委譲する
}
}これで、あなたのコードは期待されるインターフェースを通じて古いプリンターで動作できるようになります。
OldPrinter legacy = new OldPrinter();
IPrinter printer = new PrinterAdapter(legacy);
printer.Print("Hello"); // 旧: Helloアダプターパターンは、サードパーティライブラリやレガシーコードをアプリケーションに統合する際に非常に有用です。クライアントコードは IPrinter インターフェースを介して動作するため、古い互換性のないクラスが舞台裏で実際の処理を行っていることをまったく意識する必要がありません。
チャレンジ
簡単Adapter パターンを使用してメディアプレーヤーシステムを構築しましょう。MP3ファイルを再生する既存のオーディオライブラリがありますが、アプリケーションには異なるメディアタイプを処理できる統一されたインターフェースが必要です。このギャップを埋めるためのアダプターを作成し、最新のメディアプレーヤーがレガシーオーディオライブラリとシームレスに連携できるようにします。
コードは以下の3つのファイルに分けて構成します:
LegacyAudio.cs: これは変更できない既存のライブラリを表します。MediaSystem名前空間にLegacyAudioPlayerクラスを作成し、"Playing MP3: {filename}"という文字列を返すPlayMp3(string filename)メソッドを定義してください。これが「アダプティー(適合される側)」、つまり互換性のないインターフェースを持つクラスになります。MediaAdapter.cs: 同じ名前空間にインターフェースとアダプターを作成します。文字列を返すPlay(string filename)メソッドを持つIMediaPlayerインターフェースを定義してください。次に、IMediaPlayerを実装するMediaPlayerAdapterクラスを作成します。アダプターは(コンストラクタを通じて渡される)LegacyAudioPlayerインスタンスをラップし、Playの呼び出しをレガシープレーヤーのPlayMp3メソッドに変換する必要があります。Program.cs: レガシープレーヤーを作成し、それをアダプターでラップし、IMediaPlayerインターフェースを通じてアダプターを使用することで、すべてをまとめます。これにより、クライアントコードが最新のインターフェースで動作し、アダプターが舞台裏でレガシーシステムとの通信を処理する様子が示されます。
1つの入力を受け取ります:
- 再生するファイル名(例:
song.mp3)
LegacyAudioPlayer を作成し、それを MediaPlayerAdapter でラップしてから、IMediaPlayer インターフェースを使用してアダプターの Play を呼び出し、その結果を出力してください。
例えば、入力が favorite_track.mp3 の場合、出力は以下のようになります:
Playing MP3: favorite_track.mp3このパターンの素晴らしい点は、メインコードが IMediaPlayer とのみやり取りし、レガシーオーディオライブラリが実際の作業を行っていることを関知しないことです。将来、別のレガシーライブラリをサポートする必要が生じた場合でも、クライアントコードを変更することなく、新しいアダプターを作成するだけで済みます!
チートシート
Adapter パターンは、2つのクラス間の架け橋として機能することで、互換性のないインターフェースを連携させる構造パターンです。
このパターンは、あるクラスのインターフェースをクライアントが期待するインターフェースに変換します。既存のクラスを使用したいが、そのインターフェースがコードのニーズと一致しない場合に便利です。
基本構造:
// 互換性のないインターフェースを持つ既存のクラス (Adaptee)
public class OldPrinter
{
public void PrintMessage(string msg) => Console.WriteLine($"Old: {msg}");
}
// コードが期待するターゲットインターフェース
public interface IPrinter
{
void Print(string text);
}
// Adapter がそのギャップを埋めます
public class PrinterAdapter : IPrinter
{
private OldPrinter _oldPrinter;
public PrinterAdapter(OldPrinter oldPrinter)
{
_oldPrinter = oldPrinter;
}
public void Print(string text)
{
_oldPrinter.PrintMessage(text); // 古いインターフェースに委譲します
}
}使用法:
OldPrinter legacy = new OldPrinter();
IPrinter printer = new PrinterAdapter(legacy);
printer.Print("Hello"); // Old: Helloクライアントコードはターゲットインターフェースを介して動作し、背後で互換性のないクラスが実際の処理を行っていることを意識しません。このパターンは、サードパーティのライブラリやレガシーコードを統合する際に特に価値があります。
自分で試してみよう
using System;
using MediaSystem;
class Program
{
public static void Main(string[] args)
{
// ファイル名の入力を読み込む
string filename = Console.ReadLine();
// TODO: LegacyAudioPlayer のインスタンスを作成する
// TODO: それを MediaPlayerAdapter でラップする
// TODO: IMediaPlayer インターフェースを介してアダプターを使用する
// filename を指定して Play を呼び出した結果を出力する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。