✅ Adapter Pattern
- 서로 다른 인터페이스를 가진 클래스들끼리 함께 동작할 수 있도록 중간에 어댑터 클래스를 두어 변환(연결)시켜주는 디자인 패턴
- 인터페이스가 맞지 않는 클래스들을 서로 연결해주는 구조 패턴
- 호환되지 않는 두 개의 클래스를 중간에서 연결해주는 중재자역할
🔧 언제 쓰나요?
- 기존 코드는 그대로 두고,
- 새로운 코드가 기존 인터페이스와 맞지 않을 때
- 레거시 시스템 재사용이 필요한 경우
🎯 목적
호환되지 않는 인터페이스들을 맞춰서 기존 코드에 수정 없이 새 기능을 끼워넣을 수 있게 한다.
🧩 구성 요소
구성 요소 | 설명 |
Target (목표 인터페이스) | 클라이언트가 기대하는 인터페이스 |
Adaptee (적용 대상) | 기존에 있는 클래스 (호환되지 않음) |
Adapter (어댑터) | Target 을 구현하고, 내부적으로 Adaptee 를 호출하여 연결 |
💡 구조 그림
Client | V Target (인터페이스) ↑ Adapter (중간 변환기) ↓ Adaptee (호환 안 되는 기존 클래스)
✅ 예시 코드 (Java)
기존 클래스 (Adaptee)
class OldCharger { public void chargeWithOldCable() { System.out.println("구형 케이블로 충전 중..."); } }
클라이언트가 원하는 인터페이스 (Target)
interface NewCharger { void chargeWithUSB(); }
어댑터 클래스
class ChargerAdapter implements NewCharger { private OldCharger oldCharger; public ChargerAdapter(OldCharger oldCharger) { this.oldCharger = oldCharger; } @Override public void chargeWithUSB() { // 내부적으로 구형 메서드 호출 oldCharger.chargeWithOldCable(); } }
사용 예
public class Main { public static void main(String[] args) { OldCharger oldCharger = new OldCharger(); NewCharger adapter = new ChargerAdapter(oldCharger); adapter.chargeWithUSB(); // 출력: 구형 케이블로 충전 중... } }
📌 장점
- 기존 코드 수정 없이 새로운 인터페이스에 맞춰 사용 가능
- 레거시 시스템 재사용 가능
- SOLID의 **OCP(개방-폐쇄 원칙)**에 부합
❗️ 단점
- 클래스 수 증가 (복잡도 약간 증가)
- 다중 어댑터 조합 시 구조가 복잡해질 수 있음
🔄 예시로 기억하기 좋은 사례
실제 사례 | 설명 |
콘센트 변환기 | 한국 플러그 → 유럽 플러그 변환 |
ListAdapter in Android | 데이터 → RecyclerView UI 변환 |
InputStreamReader in Java | 바이트 스트림(InputStream) → 문자 스트림(Reader) |
실습1
OuterTiger (외부 요소)
package ex03; // 외부 요소 - 내가 만들지 않아서 손 댈 수 없음! public class OuterTiger { private String fullName = "호랑이"; public String getFullName() { return fullName; } }
TigerAdapter
package ex03; /* * 어뎁터를 만들땐 클래스명만 봐도 어뎁터인 것을 알 수 있게 해야함 * */ public class TigerAdapter extends Animal { private OuterTiger outerTiger; // 의존성 주입 public TigerAdapter(OuterTiger outerTiger) { this.outerTiger = outerTiger; } // 동일한 함수명으로 끼워 맞춘다 public String getName() { return outerTiger.getFullName(); } }
App
package ex03; public class App { public static void main(String[] args) { Mouse m1 = new Mouse(); Doorman d1 = new Doorman(); d1.쫓아내(m1); Cat c1 = new Cat(); d1.쫓아내(c1); TigerAdapter t1 = new TigerAdapter(new OuterTiger()); d1.쫓아내(t1); } }
실습2
UserProfile
package ex99; // 추상화 public class UserProfile { public int getId() { return 0; } public String getUsername() { return "정원"; } }
Authentication
package ex99; // 추상화 public abstract class Authentication { public abstract UserProfile login(); }
NaverProfile
package ex99; public class NaverProfile extends UserProfile { private int id; private String mainName; public NaverProfile(int id, String mainName) { this.id = id; this.mainName = mainName; } @Override public int getId() { return id; } public String getUsername() { return mainName; } }
NaverAuthentication
package ex99; public class NaverAuthentication extends Authentication { @Override public UserProfile login() { System.out.println("네이버 로그인 완료"); return new NaverProfileAdapter(new NaverProfile(2, "haha")); } }
App
package ex99; import java.util.Scanner; // FacebookProfile -> uId, uName (이게 추가될때 기존 코드를 손되지 않을 수 있게 하는게 목표) public class App { static void mainPage(UserProfile profile) { System.out.println("로그인 하신 아이디는 " + profile.getUsername() + " 입니다."); } public static void main(String[] args) { Scanner sc = new Scanner(System.in); String provider = sc.nextLine(); // 객체 생성 전담 클래스를 만들어서 처리해주면 좋다. Authentication authentication; if (provider.equals("google")) { authentication = new GoogleAuthentication(); } else if (provider.equals("kakao")) { authentication = new KakaoAuthentication(); } else if (provider.equals("facebook")) { authentication = new FacebookAuthentication(); } else if (provider.equals("naver")) { authentication = new NaverAuthentication(); } else { System.out.println("지원하지 않는 Provider 입니다."); return; } UserProfile userProfile = authentication.login(); mainPage(userProfile); } }

실습3
Emai
package ex98; public interface Email { int send(String to, String from, String msg); }
EmailAdapter
- Mock 객체이므로 이름을 EmailMock 라고 짓기도 함
package ex98; // Mock 객체 - 가짜 public class EmailAdapter implements Email { public int send(String to, String from, String msg) { System.out.println("진짜 이메일 전송됨"); return 1; } }
OuterEmail
package ex98; public class OuterEmail implements Email { public int send(String to, String from, String msg) { System.out.println("진짜 이메일 전송됨"); return 1; } }
App
package ex98; public class App { static int start(Email email) { return email.send("받는놈", "보내는놈", "메시지"); } public static void main(String[] args) { Mouse m1 = new Mouse(); Doorman d1 = new Doorman(); Cat c1 = new Cat(); // 동물이 침입하려고 했어요!! (email 보내고 쫓아내야함) int result = start(new OuterEmail()); if (result == 1) { d1.쫓아내(m1); d1.쫓아내(c1); } } }
예제
package ex97; // 인터페이스는 내부의 모든 메서드를 구현해야한다 interface 나이프 { void 킬(); void 요리(); } // adapter // interface 내부의 메서드를 걸러내는 용도로도 만들 수 있다 // 구현할 메서드를 제외하고 override 하면, 해당 adapter를 상속했을 때 구현할 메서드만 재정의하면 됨 abstract class 전투칼 implements 나이프 { @Override public void 요리() { } } abstract class 요리칼 implements 나이프 { @Override public void 킬() { } } // 덱스는 킬 메서드, 백종원은 요리 메서드만 구현가능 class 덱스 extends 전투칼 { @Override public void 킬() { } } class 백종원 extends 요리칼 { @Override public void 요리() { } } public class App { public static void main(String[] args) { } }
✅ 객체 어댑터 (Object Adapter) vs 클래스 어댑터 (Class Adapter)
항목 | 객체 어댑터 (Object Adapter) | 클래스 어댑터 (Class Adapter) |
구현 방식 | 어댑터가 Adaptee를 필드로 포함(Composition, 포함 관계) | 어댑터가 Adaptee를 상속(Inheritance, extends) |
유연성 | ✔ 더 유연함 (여러 클래스 조합 가능) | ❌ 덜 유연함 (단일 상속만 가능) |
자바 지원 | 자바에서 권장됨 (다중 상속 불가이므로) | 자바에서는 제한적 (extends는 하나만 가능) |
예시 코드 | new ChargerAdapter(new OldCharger()) | class ChargerAdapter extends OldCharger |
다형성 활용 | O | X |
1. 객체 어댑터 (Object Adapter)
🔍 개념
어댑터 클래스가 Adaptee(기존 클래스)를 포함(Composition)하여 클라이언트가 기대하는 인터페이스를 구현하는 방식
🔧 구성
Adapter
는Target
인터페이스를 구현
- 내부에
Adaptee
객체를 필드로 가지고 있음
- 메서드 호출 시, 내부의
Adaptee
인스턴스를 사용하여 위임
📌 예시 구조
class Adapter implements Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } @Override public void request() { adaptee.specificRequest(); // 변환 로직 } }
✅ 장점
- 자바에서 선호됨 (다중 상속 불가)
Adaptee
의 서브클래스도 자유롭게 사용 가능
- 더 유연하고 재사용성 높음
2. 클래스 어댑터 (Class Adapter)
🔍 개념
어댑터 클래스가 Adaptee를 상속(Inheritance)하고, 동시에Target
인터페이스도 구현하는 방식
🔧 구성
Adapter
클래스는Adaptee
를extends
하고Target
을implements
- 메서드 호출은 상속받은 메서드를 직접 사용하거나 오버라이드함
📌 예시 구조
class Adapter extends Adaptee implements Target { @Override public void request() { specificRequest(); // Adaptee 메서드 사용 } }
⚠️ 제약
- 자바는 다중 상속을 지원하지 않기 때문에
이미 다른 클래스를 상속 중이라면 이 방식 사용 불가
🔄 두 방식 비교 요약
항목 | 객체 어댑터 | 클래스 어댑터 |
구현 방법 | 포함 (Composition) | 상속 (Inheritance) |
유연성 | ✔ 높음 (여러 조합 가능) | ❌ 낮음 (상속 한 번만 가능) |
자바에서 추천 | ✅ 선호됨 | 제한적 사용 |
구조적 복잡도 | 조금 복잡 | 구조 단순 |
재사용성 | 높음 | 낮음 |
다형성 | 지원 (필드 교체 가능) | 제한적 |
✅ 어댑터 패턴 vs 전략 패턴 비교
항목 | Adapter Pattern | Strategy Pattern |
목적 | 기존 코드의 인터페이스를 변경하여 호환성 확보 | 실행 중에 행위를 바꿀 수 있게 설계 |
관계 | 호환되지 않는 인터페이스를 중간에 맞춤 | 여러 알고리즘을 인터페이스로 캡슐화 |
유연성 포인트 | 기존 클래스 변경 없이 새 코드와 연결 | 런타임에 행위를 쉽게 교체 |
사용 위치 | 주로 레거시 코드 재사용, 외부 라이브러리 연결 시 | 동작 전략을 바꾸고 싶을 때, 유연한 설계 요구 시 |
💡 간단한 비유
패턴 | 예시 비유 |
어댑터 | 110V 플러그를 220V에 꽂기 위한 변환기 |
전략 | ‘결제 방식’을 런타임에 카드/페이코/카카오로 바꾸는 구조 |
✅ 핵심 요약
비교 대상 | 객체 어댑터 | 클래스 어댑터 | 전략 패턴 |
구현 방법 | 포함 (Composition) | 상속 (Inheritance) | 인터페이스 기반 전략 교체 |
사용 목적 | 인터페이스 호환 | 인터페이스 호환 | 행위 변경 |
특징 | 유연하고 범용적 | 구조 간단하나 제한적 | 실행 중 변경 유연 |
Share article