준비하는 대학생

[Design Pattern] Adapter Pattern(어댑터 패턴) 본문

Programming/Design pattern

[Design Pattern] Adapter Pattern(어댑터 패턴)

Bangii 2023. 6. 2. 09:19

어댑터 패턴이란?

어댑터 패턴은 클래스의 인터페이스를 사용자가 기대하는 다른 인터페이스로 변환하는 디자인 패턴입니다. 이를 통해 인터페이스가 호환되지 않는 클래스들이 함께 작동할 수 있습니다. 실생활에서 어댑터의 예시를 들어보면, 여러분이 여행을 가서 전기 플러그가 다른 국가에서도 장비를 사용할 수 있게 해주는 전원 어댑터를 생각해 볼 수 있습니다. 이처럼 어댑터 패턴도 두 클래스 사이의 '번역기' 역할을 하며, 이들이 서로 상호 작용할 수 있게 해줍니다.

어댑터 패턴의 구성요소

어댑터 패턴에는 다음과 같은 구성요소가 있습니다.

  • Target: 클라이언트가 호출하는 인터페이스를 정의합니다.
  • Adaptee: 클라이언트가 사용하고자 하는, 하지만 현재 인터페이스가 호환되지 않는 클래스를 말합니다.
  • Adapter: Adaptee의 인터페이스를 Target 인터페이스로 변환합니다.

어댑터 패턴의 사용 사례

1) 기존 시스템과 새 시스템의 호환성 문제

시스템을 업그레이드하거나 새로운 시스템으로 이동하려고 할 때 종종 인터페이스의 호환성 문제가 발생합니다. 이럴 때 어댑터 패턴을 사용하여 기존의 코드를 건드리지 않고 새로운 시스템에 맞게 재사용할 수 있습니다.

2) 라이브러리나 외부 시스템과의 연결

외부 라이브러리를 사용하거나 다른 시스템과 통신할 때도 어댑터 패턴이 유용합니다. 외부 시스템이 제공하는 인터페이스와 우리 시스템의 인터페이스가 항상 일치하지 않습니다. 어댑터 패턴을 이용하면 두 시스템 간의 차이를 보완하여 서로 상호작용하게 만들 수 있습니다.

예시

아래에는 어댑터 패턴을 활용한 간단한 자바 코드 예시를 준비했습니다. 이 예시에서는 두 가지 다른 인터페이스를 가진 오디오 플레이어와 미디어 플레이어를 함께 사용하는 상황을 상상해 봅시다.

우리의 목표는 AudioPlayer 클래스가 MediaPlayer 인터페이스를 통해 vlc와 mp4 파일도 재생할 수 있게 하는 것입니다.

1. MediaPlayer Interface

public interface MediaPlayer {
   public void play(String audioType, String fileName);
}

2. AdvancedMediaPlayer Interface

public interface AdvancedMediaPlayer {	
   public void playVlc(String fileName);
   public void playMp4(String fileName);
}

3. VlcPlayer와 Mp4Player 클래스(Adaptee)

public class VlcPlayer implements AdvancedMediaPlayer{
   @Override
   public void playVlc(String fileName) {
      System.out.println("Playing vlc file. Name: "+ fileName);		
   }

   @Override
   public void playMp4(String fileName) {
      // Do nothing
   }
}

public class Mp4Player implements AdvancedMediaPlayer{

   @Override
   public void playVlc(String fileName) {
      // Do nothing
   }

   @Override
   public void playMp4(String fileName) {
      System.out.println("Playing mp4 file. Name: "+ fileName);
   }
}

4. MediaAdapter 클래스(Adapter)

public class MediaAdapter implements MediaPlayer {

   AdvancedMediaPlayer advancedMusicPlayer;

   public MediaAdapter(String audioType){
   
      if(audioType.equalsIgnoreCase("vlc") ){
         advancedMusicPlayer = new VlcPlayer();			
         
      }else if (audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer = new Mp4Player();
      }	
   }

   @Override
   public void play(String audioType, String fileName) {
   
      if(audioType.equalsIgnoreCase("vlc")){
         advancedMusicPlayer.playVlc(fileName);
      }
      else if(audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer.playMp4(fileName);
      }
   }
}

 

5. AudioPlayer 클래스(Target)

 
 
 
public class AudioPlayer implements MediaPlayer {
   MediaAdapter mediaAdapter; 

   @Override
   public void play(String audioType, String fileName) {		

      // Inbuilt support to play mp3 music files
      if(audioType.equalsIgnoreCase("mp3")){
         System.out.println("Playing mp3 file. Name: " + fileName);			
      } 
      
      // MediaAdapter is providing support to play other file formats
      else if(audioType.equalsIgnoreCase("vlc") 
         || audioType.equalsIgnoreCase("mp4")){
         mediaAdapter = new MediaAdapter(audioType);
         mediaAdapter.play(audioType, fileName);
      }
      
      else{
         System.out.println("Invalid media. " 
            + audioType + " format not supported");
      }
   }   
}

 

* equalsIgnoreCase: String 클래스의 메서드로 두 문자열을 비교하는 메서드(대소문자 무시)

이 코드를 실행하면 AudioPlayer는 mp3 파일을 직접 재생하고, vlc와 mp4 파일은 MediaAdapter를 통해 재생합니다. 이렇게 어댑터 패턴을 사용하면 기존 클래스의 코드를 변경하지 않고도 새로운 기능을 추가할 수 있게 됩니다. 이 코드를 실행하는 메인 클래스는 다음과 같습니다.

public class AdapterPatternDemo {
   public static void main(String[] args) {
      AudioPlayer audioPlayer = new AudioPlayer();

      audioPlayer.play("mp3", "beyond the horizon.mp3");
      audioPlayer.play("mp4", "alone.mp4");
      audioPlayer.play("vlc", "far far away.vlc");
      audioPlayer.play("avi", "mind me.avi");
   }
}

출력

Playing mp3 file. Name: beyond the horizon.mp3
Playing mp4 file. Name: alone.mp4
Playing vlc file. Name: far far away.vlc
Invalid media. avi format not supported

본 예제에서 볼 수 있듯이, 어댑터 패턴은 기존 시스템의 변경 없이 새로운 기능을 추가하거나 기존 시스템과 외부 시스템을 매끄럽게 통합하는 데 매우 유용합니다.

어댑터 패턴 종류

어댑터 패턴은 '클래스 어댑터 패턴'과 '객체 어댑터 패턴'의 두 가지 형태로 나눌 수 있습니다.

  1. 클래스 어댑터 패턴
    : 이 패턴은 상속을 이용해서 Adaptee 클래스의 인터페이스를 Target 인터페이스로 변환합니다. 이 패턴의 문제점은 자바와 같은 단일 상속 언어에서는 하나의 클래스만 상속할 수 있기 때문에, 한 번에 여러 Adaptee를 대상으로 할 수 없습니다.
  2. 객체 어댑터 패턴
    : 이 패턴은 합성을 사용해서 여러 개의 Adaptee를 대상으로 할 수 있습니다. 객체 어댑터는 Target 인터페이스를 구현하고, Adaptee 인스턴스를 포함하는 클래스를 만듭니다. 이 방식은 클래스 어댑터의 제약 사항을 해결하며, 어떤 클래스가 Adaptee로 사용될지를 런타임에 결정할 수 있는 유연성을 제공합니다.

자바에서의 어댑터 패턴

자바는 단일 상속만 지원하기 때문에 클래스 어댑터 패턴을 직접 구현하는 것은 불가능합니다. 하지만, 인터페이스를 통한 다중 상속이 가능하므로 이를 통해 클래스 어댑터 패턴과 비슷한 효과를 낼 수 있습니다.

그럼에도 불구하고, 대부분의 경우에는 객체 어댑터 패턴이 더 권장됩니다. 객체 어댑터 패턴은 클래스 어댑터 패턴보다 더 유연하며, 어떤 클래스가 Adaptee로 사용될지를 런타임에 결정할 수 있기 때문입니다.

이전에 제공한 자바 코드 예제는 바로 객체 어댑터 패턴을 사용한 것입니다. MediaAdapter는 MediaPlayer 인터페이스를 구현하고, AdvancedMediaPlayer의 인스턴스를 내부에 포함하고 있습니다. 이로 인해 MediaAdapter는 AdvancedMediaPlayer의 메서드를 사용하여 MediaPlayer의 인터페이스를 충족시킬 수 있습니다.

따라서 자바에서는 상황에 따라 클래스 어댑터 패턴과 비슷한 효과를 내기도 하지만, 대부분의 경우 객체어댑터 패턴을 선호하는 편입니다.

어댑터 패턴의 장단점

장점

  • 재사용성 증가: 기존에 작성된 코드나 클래스를 쉽게 재사용할 수 있습니다. 즉, 코드의 중복을 줄이고 유지 보수를 용이하게 합니다.
  • 확장성: 새로운 클래스를 추가하거나 기존 클래스를 변경하는 것이 용이합니다. 이는 소프트웨어의 확장성을 향상시키는데 도움이 됩니다.
  • 결합도 감소: 어댑터를 사용함으로써 클래스 간의 결합도를 낮출 수 있으며, 따라서 하나의 클래스가 변경되었을 때 그 영향을 최소화할 수 있습니다.

단점

  • 코드 복잡성 증가: 어댑터 패턴을 사용하면 클래스의 수가 증가하고, 이로 인해 코드가 복잡해질 수 있습니다.
  • 어댑터 오버헤드: 어댑터는 하나의 인터페이스를 다른 인터페이스로 변환하는 과정에서 약간의 성능 저하가 발생할 수 있습니다.

결론

어댑터 패턴은 객체 지향 프로그래밍에서 매우 유용한 디자인 패턴입니다. 서로 다른 클래스 또는 시스템 간의 상호 작용을 가능하게 하며, 기존 코드의 재사용성을 높이는 데 큰 도움이 됩니다. 그러나 모든 상황에서 어댑터 패턴이 최적의 해결책이 되는 것은 아닙니다. 실제 문제와 요구 사항에 따라 적절한 디자인 패턴을 선택하는 것이 중요합니다.

Comments