Adapter
Adapter는 서로 호환되지 않는 두 인터페이스를 중간에서 연결해 주는 구조 패턴입니다. 클라이언트가 기대하는 인터페이스(Target)와 실제 사용하려는 클래스(Adaptee)의 인터페이스가 다를 때, Adapter가 그 사이에서 호출을 변환합니다. 전원 플러그 어댑터와 같은 원리입니다. 한국 콘센트에 미국 플러그를 꽂을 수 없지만, 어댑터를 끼우면 전기는 그대로 흐릅니다. 소프트웨어에서도 마찬가지로, 인터페이스만 맞지 않을 뿐 내부 기능은 쓸 수 있는 상황이 Adapter의 출발점입니다.
▶아키텍처 다이어그램
🔍 구조 다이어그램점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다
프로젝트가 커지면 외부 라이브러리, 레거시 모듈, 서드파티 API를 가져다 써야 하는 상황이 반복됩니다. 문제는 이들의 인터페이스가 내 코드가 기대하는 형태와 다르다는 점입니다. 메서드 이름이 다르고, 파라미터 순서가 다르고, 반환 타입이 다릅니다. 이때 클라이언트 코드를 외부 인터페이스에 맞춰 고치면, 나중에 라이브러리를 교체할 때 클라이언트 전체가 다시 영향을 받습니다. 반대로 외부 코드를 수정하자니 소스가 없거나, 수정하면 업데이트를 받기 어려워집니다. 양쪽 다 건드리지 않으면서 연결할 방법이 필요합니다.
GoF(Gang of Four)가 1994년에 정리한 23개 디자인 패턴 중 하나입니다. 객체지향 프로그래밍이 보편화되면서, 서로 다른 팀이 만든 클래스 라이브러리를 조합해 쓰는 일이 잦아졌습니다. 소스 코드를 직접 수정할 수 없는 상용 라이브러리가 늘어나면서, 인터페이스 불일치를 코드 변경 없이 해결하는 구조적 방법이 필요해졌습니다. 이 압력은 오늘날에도 똑같습니다. npm 패키지, REST API, 레거시 마이크로서비스처럼 '동작은 하지만 인터페이스가 안 맞는' 코드를 연결하는 상황은 매일 생깁니다.
Adapter의 핵심은 세 역할로 나뉩니다. Client는 Target 인터페이스의 메서드만 알고 호출합니다. Adaptee는 실제 기능을 갖고 있지만 Target과 다른 인터페이스를 갖고 있습니다. Adapter는 Target 인터페이스를 구현하면서, 내부에서 Adaptee의 메서드를 호출해 결과를 변환합니다. 동작 순서는 이렇습니다. 1. Client가 Target 인터페이스의 `request()`를 호출합니다. 2. Adapter의 `request()` 안에서 Adaptee의 `specificRequest()`를 호출합니다. 3. Adaptee가 돌려준 결과를 Client가 기대하는 형태로 변환해 반환합니다. 구현 방식은 두 가지입니다. 오브젝트 어댑터는 Adaptee를 필드로 갖고 합성(composition)으로 위임합니다. 클래스 어댑터는 Adaptee를 상속받아 메서드를 오버라이드합니다. 대부분의 실무 상황에서는 합성 방식이 더 유연합니다. 상속은 다중 상속이 가능한 언어에서만 자연스럽고, 하나의 Adaptee에 강하게 묶인다는 제약이 있습니다.
Adapter와 Facade는 둘 다 기존 코드를 감싸서 다른 인터페이스를 노출한다는 점에서 비슷해 보입니다. 하지만 목적이 다릅니다. Adapter는 호환되지 않는 인터페이스를 기존 인터페이스에 맞추는 것이 목적입니다. 감싸는 대상이 하나이고, 인터페이스 변환이 핵심입니다. Facade는 복잡한 서브시스템 여러 개를 단순화된 하나의 진입점으로 묶는 것이 목적입니다. Adapter와 Decorator도 구조가 유사합니다. 둘 다 대상 객체를 감싸지만, Adapter는 인터페이스를 바꾸고, Decorator는 인터페이스를 유지하면서 기능을 추가합니다. Adapter를 쓸지 판단하는 기준은 '인터페이스가 안 맞아서 연결이 안 되는가'입니다. 기능이 부족한 것이 아니라 형태가 안 맞을 때 Adapter가 적합합니다.
자주 비교하는 개념
Facade
복잡한 서브시스템에 단순한 진입점을 만드는 구조 패턴
Adapter는 호환되지 않는 하나의 인터페이스를 변환하고, Facade는 복잡한 여러 서브시스템을 단순한 진입점 하나로 묶습니다.
Decorator
객체에 기능을 동적으로 덧붙이는 구조 패턴
Adapter는 인터페이스를 다른 형태로 바꾸고, Decorator는 같은 인터페이스를 유지하면서 기능을 덧붙입니다.
Proxy
실제 객체에 대한 접근을 제어하는 대리 객체 패턴
Adapter는 인터페이스를 변환해 호환성을 만들고, Proxy는 같은 인터페이스를 유지하면서 접근을 제어합니다.
Adapter는 시스템 경계에서 자주 등장합니다. 결제 게이트웨이를 교체할 때 기존 코드의 `PaymentService` 인터페이스는 유지하면서, 새 게이트웨이의 API를 Adapter로 감싸면 클라이언트 코드는 변경 없이 동작합니다. ORM을 바꾸거나 외부 인증 서비스를 교체할 때도 같은 접근이 가능합니다. 테스트에서도 유용합니다. 실제 HTTP 클라이언트 대신 테스트용 목(mock) 구현을 같은 인터페이스로 끼워 넣을 때 Adapter 구조가 자연스럽게 나타납니다. 다만 Adapter가 너무 많아지면 시스템의 인터페이스 설계 자체를 재검토할 신호일 수 있습니다. 인터페이스 불일치가 구조적이라면, 변환 레이어를 계속 쌓는 것보다 공통 인터페이스를 재설계하는 편이 장기적으로 나은 경우도 있습니다.