Menu
Coddy logo textTech

Adapterパターン

CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 97/104。

Adapter パターンにより、互換性のないインターフェースを連携させることができます。これは2つのクラス間の橋渡し役として機能し、一方のクラスのインターフェースを、クライアントが期待するインターフェースに変換します。これは、システムのインターフェースに適合しない legacy コードやサードパーティライブラリを統合する場合に特に役立ちます。

必要な処理を行う既存のクラスがあるものの、そのインターフェースがコードの期待するものと一致していないとします。どちらか一方を変更する代わりに、既存のクラスをラップして呼び出しを変換するアダプターを作成します。

#include <iostream>
#include <string>

// 互換性のないインターフェースを持つ既存のクラス
class LegacyPrinter {
public:
    void printMessage(const std::string& msg) {
        std::cout << "Legacy: " << msg << "\n";
    }
};

// クライアントが期待するターゲットインターフェース
class Printer {
public:
    virtual void print(const std::string& text) = 0;
    virtual ~Printer() = default;
};

// Adapter は LegacyPrinter をラップし、Printer インターフェースを実装する
class PrinterAdapter : public Printer {
    LegacyPrinter& legacy;
public:
    PrinterAdapter(LegacyPrinter& lp) : legacy(lp) {}
    
    void print(const std::string& text) override {
        legacy.printMessage(text);  // 呼び出しを変換する
    }
};

int main() {
    LegacyPrinter oldPrinter;
    PrinterAdapter adapter(oldPrinter);
    
    // クライアントは期待されるインターフェースを使用する
    adapter.print("Hello World");
}

adapter は LegacyPrinter への参照を保持し、Printer インターフェースを実装します。print() が呼び出されると、printMessage() に委譲します。クライアントコードは、下位にあるレガシー実装を知らなくても、Printer インターフェースを使って動作します。

既存の class を使用したいものの、そのインターフェースが必要なものと一致しない場合、または incompatible なインターフェースを持つクラスと連携する再利用可能な class を作成する必要がある場合は、Adapter を使用します。

challenge icon

チャレンジ

簡単

異なるオーディオライブラリのインターフェースを橋渡しするMedia Player Adapterシステムを構築しましょう。古いオーディオライブラリを最新のメディアプレーヤーアプリケーションに統合するとします。古いライブラリは完全に動作しますが、そのインターフェースはプレーヤーが期待するものと一致していません。まさにこのような場面でAdapterパターンが力を発揮します。

コードを4つのファイルに分けて整理します。

  • MediaPlayer.h:最新のメディアプレーヤーが期待する対象インターフェースをDefineします。

    次の純粋仮想メソッドを持つabstractなMediaPlayerクラスをCreateします。

    • play(const std::string& filename):指定されたファイルを再生します
    • stop():再生を停止します
    • setVolume(int level):音量を設定します(0-100)

    仮想デストラクタをIncludeします。

  • LegacyAudioLib.h:互換性のないインターフェースを持つ既存の「legacy」オーディオライブラリをCreateします。

    LegacyAudioLibクラスは、プレーヤーが期待するものとは異なる方法で動作する古いライブラリを表します。次の機能を持たせます。

    • loadAudio(const std::string& path)Legacy: Loading [path]を出力します
    • startPlayback()Legacy: Playback startedを出力します
    • stopPlayback()Legacy: Playback stoppedを出力します
    • adjustVolume(double percentage):小数(0.0から1.0)を受け取り、Legacy: Volume set to [percentage]を出力します(小数値を表示します)
  • AudioAdapter.h:2つのインターフェースを橋渡しするadapterを構築します。

    AudioAdapterクラスはMediaPlayerを継承し、LegacyAudioLibオブジェクトへの参照を保持します。adapterはmodernなインターフェースからの呼び出しをlegacyのインターフェースへ変換します。

    • play()はlegacyライブラリ上でloadAudio()startPlayback()の両方を呼び出します
    • stop()stopPlayback()にdelegateします
    • setVolume()は整数(0-100)を受け取りますが、legacyライブラリは小数(0.0-1.0)をexpectsするため、変換する必要があります
  • main.cpp:adapterの動作を示します。

    2つの入力を読み取ります。

    1. 再生するファイル名(文字列)
    2. 音量レベル(整数、0-100)

    LegacyAudioLibのinstanceと、それをラップするAudioAdapterをCreateします。次に、MediaPlayerインターフェースを介してadapterを使用し(MediaPlayerポインターとして保持します)、次の操作を行います。

    1. 入力されたレベルに音量を設定する
    2. 入力されたファイル名を再生する
    3. 再生を停止する

たとえば、入力がsong.mp375の場合:

Legacy: Volume set to 0.75
Legacy: Loading song.mp3
Legacy: Playback started
Legacy: Playback stopped

入力がpodcast.wav50の場合:

Legacy: Volume set to 0.5
Legacy: Loading podcast.wav
Legacy: Playback started
Legacy: Playback stopped

clientコードが完全にMediaPlayerインターフェースだけを使って動作していることに注目してください。clientは、実際の処理を裏側でlegacyライブラリが行っていることをまったく知りません。adapterが、整数の音量を小数に変換する処理も含め、すべての変換を処理します。これがAdapterパターンの本質です。どちら側も変更することなく、互換性のないインターフェースを連携させます。

自分で試してみよう

#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "LegacyAudioLib.h"
#include "AudioAdapter.h"

using namespace std;

int main() {
    // 入力を読み取る
    string filename;
    int volumeLevel;
    cin >> filename;
    cin >> volumeLevel;
    
    // TODO: LegacyAudioLib のインスタンスを作成する
    
    // TODO: レガシーライブラリをラップする AudioAdapter を作成する
    
    // TODO: アダプターを MediaPlayer ポインタとして格納する
    
    // TODO: MediaPlayer インターフェースを使用して:
    // 1. 音量を入力レベルに設定する
    // 2. 入力ファイル名を再生する
    // 3. 再生を停止する
    
    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: C++オンラインコンパイラ