✅ 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