어댑터 패턴
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 104개 중 97번째.
Adapter 패턴을 사용하면 호환되지 않는 인터페이스가 함께 작동할 수 있습니다. 이는 두 클래스 사이에서 브리지 역할을 하며, 한 클래스의 인터페이스를 클라이언트가 기대하는 인터페이스로 변환합니다. 이는 시스템의 인터페이스와 일치하지 않는 레거시 코드나 타사 library를 통합할 때 특히 유용합니다.
필요한 작업을 수행하는 Existing 클래스가 있지만 해당 인터페이스가 코드에서 expects하는 것과 일치하지 않는다고 상상해 보세요. 어느 한쪽도 수정하는 대신, Existing 클래스를 감싸고 호출을 변환하는 adapter를 만듭니다:
#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 interface를 구현합니다. print()가 호출되면 printMessage()에 위임합니다. client 코드는 내부의 legacy 구현에 대해 알지 못한 채 Printer interface와 함께 작동합니다.
기존 클래스를 사용하고 싶지만 해당 클래스의 인터페이스가 필요한 것과 일치하지 않을 때, 또는 호환되지 않는 인터페이스를 가진 클래스와 협력하는 재사용 가능한 클래스를 만들어야 할 때 Adapter를 사용하세요.
챌린지
쉬움서로 다른 audio library 인터페이스를 연결하는 Media Player Adapter 시스템을 만들어 보겠습니다. 레거시 audio library를 최신 media player 애플리케이션에 통합한다고 상상해 보세요. 기존 library는 완벽하게 작동하지만, 해당 인터페이스가 player가 기대하는 것과 일치하지 않습니다. 바로 이런 상황에서 Adapter 패턴이 빛을 발합니다.
코드를 네 개의 파일로 구성합니다.
MediaPlayer.h: 최신 media player가 기대하는 target 인터페이스를 Define합니다.다음 순수 가상 메서드를 포함하는 abstract
MediaPlayerclass를 Create합니다.play(const std::string& filename): 지정된 파일을 재생합니다.stop(): 재생을 중지합니다.setVolume(int level): 볼륨을 설정합니다(0-100).
virtual destructor를 Include합니다.
LegacyAudioLib.h: incompatible 인터페이스를 가진 기존 "legacy" audio library를 Create합니다.여러분의
LegacyAudioLibclass는 player가 기대하는 방식과 다르게 작동하는 오래된 library를 나타냅니다. 다음을 포함해야 합니다.loadAudio(const std::string& path):Legacy: Loading [path]를 출력합니다.startPlayback():Legacy: Playback started를 출력합니다.stopPlayback():Legacy: Playback stopped를 출력합니다.adjustVolume(double percentage): decimal(0.0~1.0)을 받고Legacy: Volume set to [percentage]를 출력합니다(decimal 값을 표시).
AudioAdapter.h: 두 인터페이스를 연결하는 adapter를 Build합니다.여러분의
AudioAdapterclass는MediaPlayer를 상속하고LegacyAudioLib객체에 대한 reference를 보유해야 합니다. adapter는 modern 인터페이스의 호출을 legacy 인터페이스로 변환합니다.play()는 legacy library에서loadAudio()와startPlayback()을 모두 call해야 합니다.stop()은stopPlayback()에 delegate해야 합니다.setVolume()은 integer(0-100)를 받지만 legacy library는 decimal(0.0-1.0)을 expects하므로, 이를 convert해야 합니다.
main.cpp: adapter가 작동하는 모습을 Demonstrate합니다.두 개의 inputs를 읽습니다.
- 재생할 filename(string)
- 볼륨 level(integer, 0-100)
LegacyAudioLibinstance와 이를 감싸는AudioAdapter를 Create합니다. 그런 다음 adapter를MediaPlayer인터페이스를 통해 사용합니다(MediaPlayerpointer로 저장).- input level로 볼륨 설정
- input filename 재생
- 재생 중지
예를 들어 inputs가 song.mp3와 75인 경우:
Legacy: Volume set to 0.75
Legacy: Loading song.mp3
Legacy: Playback started
Legacy: Playback stoppedinputs가 podcast.wav와 50인 경우:
Legacy: Volume set to 0.5
Legacy: Loading podcast.wav
Legacy: Playback started
Legacy: Playback stoppedclient code가 전적으로 MediaPlayer 인터페이스와 함께 작동한다는 점에 주목하세요. 실제 작업을 아래에서 legacy library가 수행한다는 사실을 전혀 알지 못합니다. adapter가 integer volume을 decimal로 convert하는 것을 포함해 모든 변환을 처리합니다. 이것이 Adapter 패턴의 본질입니다. 어느 한쪽도 수정하지 않고 incompatible 인터페이스가 함께 작동하도록 만드는 것입니다.
직접 해보기
#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;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
직접 연습해 보세요: 온라인 C++ 컴파일러