엔지니어로 가는 길

일상 속 어댑터 패턴(Adapter Pattern), Spring 속 어댑터 패턴 본문

프로그래밍/디자인 패턴

일상 속 어댑터 패턴(Adapter Pattern), Spring 속 어댑터 패턴

탐p슨 2021. 10. 24. 20:59
728x90

1. Introduction

2. 실생활 속 어댑터 패턴

3. Spring 속 어댑터 패턴

4. Reference

 

1. Introduction

어댑터 패턴은 현실의 어댑터를 코드로 표현한 것이다. 서로 호환되지 않는 두 클래스를 호환되도록 만들어준다.

 

찾아보면 어댑터 패턴에 대한 글들이 굉장히 많은데, 각각의 글마다 어댑터 패턴에 대해 *약간씩 다르게 설명하는 부분이 있다. 하지만 어댑터 패턴이라는 건, 디자인 패턴이라는 건 자주 쓰이는 구조에 편의를 위해, 의사소통을 위해 이름을 붙인 것 뿐 깐깐한 기준이 있는 것은 아닐 것이다. 어댑터 패턴을 적용해야 하는 상황은 100번 있으면 100번 모두 디테일이 다를 것이므로 설명 역시 완전히 일치할 수 없는 것 아닐까? 어댑터 패턴을 엄밀하게 정의하려고 하지 말고, 어떤 문제를 해결하고자 하는지 그 목적만 제대로 이해하고, 하나의 예시부터 충분히 이해하고 넘어가는 게 좋을 것 같다.

 

* 본문 중에 ‘약간씩 다르게 설명하는 부분’이 무엇을 말하는지 예시를 들었다.

 

2. 실생활 속 어댑터 패턴

아이폰을 쓰던 사용자가 있다고 가정해보자. 아이폰을 쓰던 사용자는 썬더볼트 충전기를 이용해서 스마트폰을 충전해왔다. 그런데 아이폰 사용자가 갤럭시로 기종을 변경했다면? 갤럭시는 썬더볼트와 호환되지 않고, USB-C 타입 충전기하고만 호환되기 때문에 더이상 갤럭시를 충전할 수 없다.

 

 

해결 방법은 두 가지이다. USB-C 타입 충전기를 사거나, 어댑터를 사거나.

 

 

위와 같이 어댑터를 이용해서 문제를 해결하는(원래 호환되지 않던 것을 호환되도록 만드는) 상황을 코드로 한 번 재현해보자.

 

상황속 주인공을 Client, 아이폰 충전기 인터페이스를 IphoneCharger, 아이폰 충전기 구현체 중 하나인 썬더볼트 충전기를 ThunderboltCharger, USB-C 타입 충전기를 UsbcCharger, 어댑터를 ThunderboltToUsbcAdapter로 표현하였다.

 

 

Target은 사용자가 가지고 있는 유일한 충전기이다. ThunderboltCharger 클래스뿐만 아니라 IphoneCharger 인터페이스까지 필요한 이유는 어댑터 패턴이 *다형성과 *다이내믹 디스패치를 이용하기 때문이다.

 

* 간단하게 말해서, 다형성이란 자식 객체는 부모 타입의 참조변수에 담길 수 있다는 것을 의미하고, 다이내믹 디스패치란 참조변수 타입이 아니라 실제 그 타입에 담겨있는 객체의 메소드를 호출하는 것을 의미한다. 결론적으로 부모 타입 참조변수에 자식 타입 참조변수가 담길 수 있고, 이 참조변수에서 부모의 메소드 A를 호출했을 경우 자식이 A를 오버라이드 했다면, 자식이 오버라이드한 메소드가 호출된다.

 

 

어댑티는 어댑터를 통해 스마트폰과 호환하고자 하는 대상이다.

 

 

호환되지 않던 두 대상을 호환되게 해주는 어댑터이다.

 

 

마지막으로 어댑터를 사용하는 클라이언트이다.

 

코드 내용을 정리하자면, Client는 IphoneCharger 인터페이스의 chargeIphone() 메소드를 통해 갤럭시를 충전하고자 한다. 하지만 IphoneCharger의 구현체인 ThunderboltCharger는 아이폰 충전을 위한 것이므로 이를 사용하여 chargeIphone() 메소드를 호출한다면 갤럭시가 충전되지 않는다.

 

그래서 ThunderboltToUsbcAdapter를 사용한다. 이는 IphoneCharger의 구현체이기 때문에 chargeIphone()을 가지고 있다. 현실의 의미로 말하자면 썬더볼트 충전기를 꽂을 수 있는 구멍이 있다. 또한 ThunderboltToUsbcAdapter는 UsbcCharger에 의존하고 있기 때문에 chargeGalaxy()를 사용할 수도 있다. 현실의 의미로는 갤럭시에 꽂을 수 있는 돌출부가 있다. 그럼 어댑터에서 할 일은 chargeIphone()이 호출되었을 때 의존하고 있는 UsbcCharger의 chargeGalaxy()를 호출해주는 것이다.

 

최종적으로 사용자는 chargeIphone()을 호출하여 chargeGalaxy()를 얻을 수 있다(아이폰 충전기이지만 갤럭시를 충전할 수 있다). 아이폰 충전기 -> 갤럭시 충전기 어댑터를 통해 아이폰 충전기로 갤럭시 충전에 성공하였다.

 

 

3. Spring 속 어댑터 패턴

Spring에서 어댑터 패턴을 적용한 코드 중 가장 간단해보이는 코드를 가져왔다.

 

 

사용자가 MultiValueMap 구현체가 아닌 Map 구현체를 가지고 MultiValueMap의 기능을 얻을 수 있도록 도와주는 Map -> MultiValueMap Adapter이다. Map 객체를 생성자에서 받고 저장해둔 뒤, MultiValueMap의 메소드가 호출되면 Map 객체를 이용하여 MultiValueMap 메소드의 스펙에 맞는 결과물을 내놓는다.


* 잘 보면 어댑터가 target을 구현하는 것이 아니라, adaptee를 구현했고, adaptee를 필드로 가지고 있는 것이 아니라 target 구현체를 가지고 있다. 그런데 앞서 든 충전기 예시(임의로 만든 것이 아니라 https://youtu.be/qG286LQM6BU 영상을 보고 각색한 예시)에서는 어댑터가 target을 구현하고, adaptee를 필드로 갖고 있다. 이런 부분이 글 서두에 말했던 미묘하게 다른 부분이다. 모든 것을 표준화해서 머릿속에 공식처럼 ‘어댑터 패턴은 이래야 해’라고 정해두려고 하지 말고, 목적만 기억하자. 어댑터 패턴은 호환되지 않는 것을 호환 가능하게 만들어주는 게 전부다. 그 목적을 달성하는 방법은 상황이 100 가지가 주어지면 100 개의 구현이 있을 수 있다.

 

4. Reference

https://youtu.be/qG286LQM6BU

https://springframework.guru/gang-of-four-design-patterns/adapter-pattern/

728x90
Comments