Table of contents
의도
어댑터는 호환되지 않는 인터페이스를 가진 객체들이 협업할 수 있도록 하는 구조적 디자인 패턴입니다.
예시
주식 시장 모니터링 앱을 만들고 있고, 이 앱은 여러 소스에서 주식 데이터를 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
정리
어댑터 클래스는 기존 클래스를 사용하고 싶지만 그 인터페이스가 나머지 코드와 호환되지 않을 때 사용하세요.
어댑터 패턴은 당신의 코드와 레거시 클래스, 또는 특이한 인터페이스가 있는 다른 클래스 간의 변환기 역할을 하는 중간 레이어 클래스를 만들 수 있도록 합니다.