Web/JS

[JS] 디자인 패턴(Design Pattern)_어댑터 패턴(Adapter Pattern)

메바동 2023. 2. 27. 23:30
728x90

"JavaScript Design Pattern" 포스팅은 ChatGPT에게 "Essential design patterns for JavaScript developers to learn"로 질문한 뒤 나온 내용에 대해 정리하는 포스팅입니다.

 


 

1. 어댑터 패턴(Adapter Pattern)

어댑터 패턴은 호환되지 않는 두 인터페이스가 함께 작동할 수 있도록 하는 디자인 패턴이다.

호환되지 않는 두 클래스 사이의 다리 역할을 하며 두 클래스의 소스 코드를 수정하지 않고도 함께 작업할 수 있도록 한다.

어댑터 패턴은 기존 클래스가 새로운 클래스 또는 인터페이스와 함께 작동해야 하지만 두 클래스의 인터페이스가 호환되지 않을 때 유용하다.

 

JavaScritp에서 어댑터 패턴은 일반적으로 타사 라이브러리 또는 API를 애플리케이션의 인터페이스에 맞게 조정하는 데 사용된다.

이는 제어할 수 없는 API와 통합하거나 호환되지 않는 인터페이스가 있는 레거시 코드로 작업할 때 유용할 수 있다.

 

JavaScript에서 어댑터 패턴은 기존 클래스를 감싸고 호환 가능한 인터페이스를 노출하는 새 클래스를 생성하여 구현된다.

어댑터 클래스는 기존 클래스의 인터페이스를 새 클래스에 필요한 인터페이스로 변환한다.

 

어댑터 패턴에는 다음 구성요소가 포함된다.

  • Client: 클라이언트는 인터페이스를 조정해야 하는 코드이다. 클라이언트는 adapter가 원하는 인터페이스를 제공할 것으로 기대하며 adapter와 상호 작용한다.
  • Target interface: 클라이언트가 adapter가 제공할 것으로 기대하는 인터페이스이다. 클라이언트가 adapter와 상호 작용하는 데 사용하는 메서드 및 속성을 정의한다.
  • Adaptee: adaptee는 호환되지 않는 인터페이스를 가진 기존 클래스 또는 API이다. 클라이언트가 필요로 하는 기능을 제공하지만 타겟 인터페이스를 구현하지 않는다.
  • Adapter: adapter는 adaptee를 타겟 인터페이스에 맞게 조정하는 클래스이다. adapter는 adaptee를 감싸고 필요한 메서드와 프로퍼티를 클라이언트에 노출한다.

 

다음은 JavaScript로 어댑터 패턴을 구현한 예이다:

 

// Target interface
class MediaPlayer {
  play(audioType, fileName) {}
}

// Adaptee
class AdvancedMediaPlayer {
  playVlc(fileName) {}
  playMp4(fileName) {}
}

// Adapter
class MediaAdapter extends MediaPlayer {
  constructor(audioType) {
    super();
    this.advancedMusicPlayer = new AdvancedMediaPlayer();
    this.audioType = audioType;
  }

  play(audioType, fileName) {
    if (this.audioType === 'vlc') {
      this.advancedMusicPlayer.playVlc(fileName);
    } else if (this.audioType === 'mp4') {
      this.advancedMusicPlayer.playMp4(fileName);
    }
  }
}

// Client
class AudioPlayer extends MediaPlayer {
  play(audioType, fileName) {
    if (audioType === 'mp3') {
      console.log('Playing mp3 file. Name: ' + fileName);
    } else if (audioType === 'vlc' || audioType === 'mp4') {
      const mediaAdapter = new MediaAdapter(audioType);
      mediaAdapter.play(audioType, fileName);
    } else {
      console.log('Invalid media. ' + audioType + ' format not supported');
    }
  }
}

// Usage
const audioPlayer = new AudioPlayer();

audioPlayer.play('mp3', 'song.mp3');
audioPlayer.play('vlc', 'song.vlc');
audioPlayer.play('mp4', 'song.mp4');
audioPlayer.play('avi', 'song.avi');

 

이 예제에서 MediaPlayer는 클라이언트가 기대하는 타겟 인터페이스이다.

AdvancedMediaPlayer는 호환 되지 않는 인터페이스를 가진 adaptee이다.

MediaAdapter는 AdavancedMediaPlayer를 MediaPlayer 인터페이스에 맞게 조정하는 adapter이다.

AudioPlayer는 adapter와 상호 작용하여 오디오 파일을 재생하는 클라이언트이다.

 

AudioPlayer 클래스의 play() 메서드는 애플리케이션에서 오디오 파일 형식이 지원되는지 확인한다.

형식이 지원되지 않으면 오류 메시지를 출력하고, 형식이 지원되는 경우 오디오 파일 형식이 포함된 새 MediaAdapter 객체를 생성하고 adapter를 사용하여 오디오 파일을 재생한다.

 

어댑터 패턴은 JavaScript에서 호환되지 않는 인터페이스를 가진 클래스를 사용할 수 있는 방법을 제공한다. 이를 통해 코드를 수정하지 않고도 타사 라이브러리나 API를 애플리케이션의 인터페이스에 맞게 조정할 수 있다.

 

 

 

2. Adapter Pattern 연습 코드

ChatGPT에게 "Give me a challenge to practice the Adapter Pattern in JavaScript."라고 질문한 뒤 나온 문제와 그에 대한 답이다.

 

스켈레톤 코드는 다음과 같다.

 

// Step 1: Define the WeatherAPI interface
class WeatherAPI {
  // TODO: Define the getWeatherData method
}

// Step 2: Define the WeatherData interface
interface WeatherData {
  temperature: number;
  humidity: number;
  pressure: number;
}

// Step 3: Implement the WeatherAPIAdapter class
class WeatherAPIAdapter {
  constructor(api) {
    // TODO: Implement the constructor
  }

  getWeather(city) {
    // TODO: Implement the getWeather method
  }
}

// Step 4: Usage
const api = new WeatherAPI();
const adapter = new WeatherAPIAdapter(api);
const data = adapter.getWeather('New York');
console.log(data.temperature); // should output the temperature in New York

 

이를 구현한 코드는 다음과 같다.

 

// Step 1: Define the WeatherAPI interface
class WeatherAPI {
  // TODO: Define the getWeatherData method
  getWeatherData(city: string) {
    switch(city) {
      case 'New York':
        return {
            name: 'New York',
            main: {
              temp: 4,
              humidity: 52,
              pressure: 1017,
            },
        }
      case 'Seoul':
        return {
          name: 'Seoul',
          main: {
            temp: 2,
            humidity: 42,
            pressure: 1020,
          },
      }
      default:
        return {
          name: 'Unknown',
          main: {
            temp: 0,
            humidity: 0,
            pressure: 0,
          }
        }
    }
  }
}

// Step 2: Define the WeatherData interface
interface WeatherData {
  temperature: number;
  humidity: number;
  pressure: number;
}

// Step 3: Implement the WeatherAPIAdapter class
class WeatherAPIAdapter {
    private api: WeatherAPI;

    constructor(api: WeatherAPI) {
    // TODO: Implement the constructor
    this.api = api;
  }

  getWeather(city: string): WeatherData{
    // TODO: Implement the getWeather method
    const data = this.api.getWeatherData(city);
    const { temp: temperature, humidity, pressure } = data.main;
    return { temperature, humidity, pressure };
  }
}

// Step 4: Usage
const api = new WeatherAPI();
const adapter = new WeatherAPIAdapter(api);
const data = adapter.getWeather('New York');
console.log(data.temperature); // should output the temperature in New York

 

JavaScript로 달라니까 왜 예제를 TypeScript로 준 걸까...

728x90