본문 바로가기

Design Patterns/영문 위키(Wikipedia)

Adapter Pattern (어댑터 패턴)

먼저 이 글은 영문 위키의 글을 번역한 글임을 알려드립니다.

영어 실력이 부족한 관계로 오역이 있을 수 있습니다.^^;

원문 주소 : http://en.wikipedia.org/wiki/Adapter_pattern



구조 패턴 - 어댑터(Apapter)



컴퓨터 프로그래밍에서 어댑터 패턴은(종종 wrapper pattern이라고 언급되기도 하고, 더 간단히 wrapper라고 불리기도 한다.) 한 클래스의 인터페이스를 호환 가능한 다른 인터페이스로 변환시키는 디자인 패턴이다. 

호환되지 않는 인터페이스때문에 일반적인 방법으로는 같이 사용할 수 없는 클래스들을 어댑터 패턴을 이용하면 같이 사용할 수 있다. 이는 클라이언트 코드에게 원래 클래스의 인터페이스를 노출시키는 대신에 어댑터의 인터페이스를 노출시킴으로써 가능해진다. 

어댑터는 어댑터 인터페이스에 대한 호출을 원래 클래스의 인터페이스 호출로 변환하고, 이를 위해 필요한 코드 량은 일반적으로 작다. 어댑터는 또한 데이터를 적절한 형태로 변환하는 역할도 한다. 

예를 들어, 여러개의 불(boolean) 값이 하나의 정수에 들어있는데(예를들면 flag같은 경우), 당신의 클라이언트 코드에서는 하나의 'true'나 'false' 값만 필요하다면 어댑터는 이 정수에서 적절한 값을 추출해내는 역할을 할 수 있다. 

다른 예는 날짜의 형태를 변경하는 것이다.(예를들면 YYYYMMDD를  MM/DD/YYYY 형태로 만들거나 DD/MM/YYYY 같은 형태로 만들 수 있다.)




구조 Structure

어댑터 패턴에는 두가지 종류가 있다. 

객체 어댑터 패턴 Object Adapter Pattern

이 타입의 어댑터 패턴에서 어댑터는 감싸야할 클래스의 객체를 포함하고 있다. 이러한 상황에서 어댑터는 감싸진 객체를 호출하는 일을 한다.






클래스 어댑터 패턴 Class Adapter Pattern

이 타입의 어댑터는 목표를 달성하기 위해 여러개의 다형성 인터페이스(polymorphic interfaces)를 사용한다. 어댑터는 구현될 예정인 인터페이스나 이미 구현된 인터페이스를 상속받아서 만들어진다. 구현될 예정인 인터페이스는 순수(pure) 인터페이스 클래스로 만들어지는 것이 일반적인데, 특히 자바같이 다중 상속(multiple inheritance)을 지원하지 않는 언어들에서 그러하다.




어댑터 패턴은 이미 존재하는 클래스가 당신이 필요로하는 일부 또는 전체 서비스를 제공하지만 당신에게 필요한 인터페이스는 제공하지 않는 상황에 유용할 수 있다. 어댑터의 좋은 실제 예는 XML 문서의 DOM(Document Object Model)의 인터페이스를 볼 수 있는  트리 구조로 바꾸는 것이다. 어댑터 디자인 패턴을 사용하는 튜토리얼 링크는 아래에 나열해 놓았다. 



더 발전된 형태의 실시간 어댑터 패턴(?) A further form of runtime Adapter Pattern

아래에 나오는 것과 같이 더 발전된 형태의 실시간으로 사용될 수 있는 어댑터 패턴이 있다 : 

classA가 약간의 데이터를 가지고(String데이터라 가정하고) classB를 지원하도록 요구되어진다. 컴파일 시의 해결책은 다음과 같다.



classB.setStringData(classA.getStringData());


string데이터의 포맷(형식)이 다양해져야 한다고 가정해보자. 컴파일 시의 해결책은 상속을 사용하는 것이다.:
Format1ClassA extends ClassA {
   public String getStringData() {
      return format(toString());
   }
}


그리고 팩토리 패턴의 도움으로 런타임시에 정확하게 표현할 형식(formatting) 객체를 만들 수 있다.



어댑터를 사용하는 방법의 진행 절차는 다음과 같다.:



(i) 중개하는 Provider 인터페이스를 정의하고, 데이터의 소스를 감싸는 Provider 인터페이스의 구현을 작성해라. 이 예에서는 ClassA가 데이터 소스가 되고, 적절한 형태를 갖춘 데이터를 만들어낸다.


public interface StringProvider {
    public String getStringData();
}
 
public class ClassAFormat1 implements StringProvider {
    ClassA classA;
 
    public ClassAFormat1(ClassA classA) {
        this.classA = classA;
    }
 
    public String getStringData() {
        return format(classA.toString());
    }
}



(ii) Provider의 특정 구현을 반환하는 Adapter클래스를 작성한다.
public class ClassAFormat1Adapter extends Adapter {
   public Object adapt(Object o) {
      return new ClassAFormat1((ClassA) o);
   }
}


(iii) Adatper를 전역 레지스트리에 등록해서 Adapter가 런타임 시에 사용될 수(can be loocked up) 있도록 한다.
AdapterFactory.getInstance().registerAdapter(ClassA.class, ClassAFormat1Adapter.class, "format1");



(iv) 당신의 코드에서 ClassA의 데이터를 ClassB로 옮기고 싶다면 다음과 같이 작성한다.


Adapter adapter = AdapterFactory.getInstance().getAdapterFromTo(ClassA.class, StringProvider.class, "format1");
StringProvider provider = (StringProvider) adapter.adapt(classA);
String string = provider.getStringData();
classB.setStringData(string);


또는 더 간결하게 하자면,

classB.setStringData(((StringProvider) AdapterFactory.getInstance().getAdapterFromTo(ClassA.class, StringProvider.class, "format1").adapt(classA)).getStringData());


(v) 만약 데이터를 두번째 형식으로 전송하고 싶다면 다른 Adapter와 Provider를 찾아서(look up) 쓸 수 있다는 것이 장점이라 볼 수 있다.


Adapter adapter = AdapterFactory.getInstance().getAdapterFromTo(ClassA.class, StringProvider.class, "format2");



(vi) 그리고 만약 ClassA로부터 ClassC의 이미지 데이터로 출력하고 싶다면 :
Adapter adapter = AdapterFactory.getInstance().getAdapterFromTo(ClassA.class, ImageProvider.class, "format1");
ImageProvider provider = (ImageProvider) adapter.adapt(classA);
classC.setImage(provider.getImage());



(vii) 이런 방법으로 Adapter와 Provider를 사용하는 것은 여러개의 뷰(views)를 제공할 수 있다. 일반적으로, 기존 객체 구조에 새로 장착할 수 있는 객체들 사이를 중개 데이터가 들어갈 수 있는 메카니즘을 허용한다.(그러니까 기존 객체 구조를 유지하고 어댑터를 이용한 메카니즘을 사용한다는 의미인 것 같은데,,, 해석하려니 말이 어렵네요-_-)




어댑터 패턴의 구현 Implementation of Adapter pattern

어댑터 패턴을 구현할 때, 명료성을 위해서 클래스 이름을 "[AdapteeClassName]To[Interface]Adapter"와 같은 식으로 만든다. 예를들면 DAOToProviderAdapter(여기에서 DAO가 AdapteeClassName에 해당하고, Provider가 Interface에 해당하겠죠?). 

이를 위해서는 adaptee클래스를 인자로 받는 생성자가 필요하다. 이 인자는 "[AdapteeClassName]To[Interface]Adapter"의 객체 멤버로 전달될 것이다.


Class SampleAdapter implements ClientClass
{
    private AdapteeClass mInstance;
    public SampleAdapter(AdapteeClass instance)
    {
         mInstance=instance;
    }
    @Override
    public void ClientClassMethod()
    {
       // call AdapteeClass's method to implement ClientClassMethod
    }
 
}