Skip to main content Link Search Menu Expand Document (external link)

Table of contents

  1. 의도
  2. 예시
  3. 해결책
  4. 어댑터의 해결책
  5. 구현 예시
  6. Output
  7. 간단한 예제로 이해하기
  8. Output
  9. 정리

의도

어댑터는 호환되지 않는 인터페이스를 가진 객체들이 협업할 수 있도록 하는 구조적 디자인 패턴입니다.

예시

주식 시장 모니터링 앱을 만들고 있고, 이 앱은 여러 소스에서 주식 데이터를 XML 형식으로 다운로드한 후 사용자에게 보기 좋은 차트들과 다이어그램들을 표시한다고 상상해 봅시다.

어느 시점에 당신은 타사의 스마트 분석 라이브러리를 통합하여 당신의 앱을 개선하기로 결정했습니다. 그런데 함정이 있습니다: 이 분석 라이브러리는 JSON 형식의 데이터로만 작동한다는 것입니다.

위 분석 라이브러리는 ‘있는 그대로’ 사용할 수 없습니다. 왜냐하면 당신의 앱과 호환되지 않는 형식의 데이터를 기다리고 있기 때문입니다.

당신은 이 라이브러리를 XML과 작동하도록 변경할 수 있으나, 그러면 라이브러리에 의존하는 일부 기존 코드가 손상될 수 있습니다.

또 처음부터 타사의 라이브러리 소스 코드에 접근하는 것이 불가능하여 위의 해결 방식을 사용하지 못할 수도 있습니다.

해결책

당신은 어댑터를 만들 수 있습니다. 어댑터는 한 객체의 인터페이스를 다른 객체가 이해할 수 있도록 변환하는 특별한 객체입니다.

어댑터는 변환의 복잡성을 숨기기 위하여 객체 중 하나를 래핑(포장)합니다. 래핑된 객체는 어댑터를 인식하지도 못합니다.

위의 예시처럼 XML로 들어온 데이터를 JSON으로 변환하는 어댑터로 래핑 할 수 있습니다.

어댑터는 데이터를 다양한 형식으로 변환할 수 있을 뿐만 아니라 다른 인터페이스를 가진 객체들이 협업하는 데에도 도움을 줄 수 있으며, 대략 다음과 같이 작동합니다:

어댑터는 기존에 있던 객체 중 하나와 호환되는 인터페이스를 받습니다. 이 인터페이스를 사용하면 기존 객체는 어댑터의 메서드들을 안전하게 호출할 수 있습니다. 호출을 수신하면 어댑터는 이 요청을 두 번째 객체에 해당 객체가 예상하는 형식과 순서대로 전달합니다. 때로는 양방향으로 호출을 변환할 수 있는 양방향 어댑터를 만드는 것도 가능합니다.

어댑터의 해결책

당신은 형식이 호환되지 않는 문제를 해결하기 위해 당신의 코드와 직접 작동하는 분석 라이브러리의 모든 클래스에 대한 XML->JSON 변환 어댑터를 만듭니다.

그 후 이러한 어댑터들을 통해서만 해당 라이브러리와 통신하도록 코드를 조정합니다. 어댑터는 호출을 받으면 들어오는 XML 데이터를 JSON 구조로 변환한 후 해당 호출을 래핑된 분석 객체의 적절한 메서드들에 전달합니다.

구현 예시

import xmltodict # python xml 라이브러리 import

class TransJsonToXML:
    """
    Json을 XML형식으로 변환
    """
    def __init__(self):
        self.name = "trans_json_to_xml"

    def json_to_xml(self, json_type=None):
        return xmltodict.unparse(json_type)

class TransXmlToJson:
    """
    XML을 JSON 형식으로 변환
    """
    def __init__(self):
        self.name = "trans_xml_to_json"

    def xml_to_json(self, xml_string=None):
        return xmltodict.parse(xml_string)

# 객체들의 인터페이스를 통합하는 어뎁터 클래스.
class Adapter:
    def __init__(self, obj, **adapted_methods):
        self.obj = obj
        # 동적으로 객체 추가
        self.__dict__.update(adapted_methods)

    # attr을 self에서 찾을 수 없을 때 호출되는 메서드.
    def __getattr__(self, attr):
        return getattr(self.obj, attr)


if __name__ == "__main__":
    # 간단한 예시로 xml 데이터와 json 데이터 생성
    json_type = {"data": {"name": "abc"}}
    xml_string = '<data><name>abc</name></data>'

    trans_xml = TransJsonToXML()
    trans_json = TransXmlToJson()

    objects = [
        Adapter(trans_xml, get_data=trans_xml.json_to_xml(json_type)),
        Adapter(trans_json, get_data=trans_json.xml_to_json(xml_string))
    ]
    
    # 각각의 결과를 한번에 출력하기 위해 아래와 같이 사용하였습니다.
    for obj in objects:
        print(f"{obj.name}: {obj.get_data}")

Output

trans_json_to_xml: <?xml version="1.0" encoding="utf-8"?><data><name>abc</name></data>
trans_xml_to_json: {'data': {'name': 'abc'}}

간단한 예제로 이해하기

class Animal:     #interface class
  def walk(self):
    pass

class Cat(Animal):
  def walk(self):
    print("cat walking")

class Dog(Animal):
  def walk(self):
    print("dog walking")
      
class Fish:    #fish doesn't have a run method
  def swim(self):
    print("fish swimming")
      
def makeWalk(animal : Animal):
  return animal.walk()
  
class FishAdapter(Animal):
  def __init__(self,fish:Fish):
    self.fish = fish
  
  def walk(self):
    return self.fish.swim()
  
if __name__ == "__main__":
    kitty = Cat()
    bingo = Dog()
    nemo = Fish()
    
    makeWalk(kitty)
    makeWalk(bingo)
    makeWalk(nemo)  # Fish 클래스엔 walk가 존재하지 않으므로 에러가 발생합니다.
    
    adapted_nemo = FishAdapter(nemo) # walk를 사용하기 위해 어뎁터 클래스를 사용합니다.
    
    makeWalk(adapted_nemo)

Output

cat walking
dog walking
fish swimming

정리

어댑터 클래스는 기존 클래스를 사용하고 싶지만 그 인터페이스가 나머지 코드와 호환되지 않을 때 사용하세요.

어댑터 패턴은 당신의 코드와 레거시 클래스, 또는 특이한 인터페이스가 있는 다른 클래스 간의 변환기 역할을 하는 중간 레이어 클래스를 만들 수 있도록 합니다.