
// ❌ 나쁜 예: 인터페이스 불일치로 사용 불가능
public class Application {
public void processData() {
// 우리는 Duck 인터페이스를 기대하는데...
Duck duck = new Turkey(); // ❌ 컴파일 에러!
duck.quack();
duck.fly();
}
}
문제점:
실제 상황 예시:
public interface Duck {
void quack(); // 클라이언트는 이 메서드를 호출하고 싶음
void fly();
}
왜 Target인가?
public interface Turkey {
void gobble(); // 다른 이름의 메서드
void fly(); // 같은 이름이지만 다른 동작
}
왜 Adaptee인가?
특징:
장점:
1. 더 유연함 - Adaptee의 서브클래스도 Adapt 가능
2. 단일 Adapter로 여러 Adaptee 처리 가능
3. Composition over Inheritance 원칙 준수
// ✅ Object Adapter
public class TurkeyAdapter implements Duck {
Turkey turkey; // Adaptee를 포함
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey; // 위임
}
public void quack() {
turkey.gobble(); // 호출을 변환
}
}
특징:
장점:
1. Adaptee의 메서드를 직접 오버라이드 가능
2. 추가 객체 생성 불필요
단점:
1. Java에서는 인터페이스만 다중 구현 가능 (클래스는 단일 상속)
2. 덜 유연함

흐름
- Client가 Target 인터페이스로 요청
- Adapter가 요청을 받음
- Adapter가 Adaptee의 메서드로 변환
- Adaptee가 실제 작업 수행
핵심 개념:
// Target Interface - 클라이언트가 기대하는 인터페이스
public interface Duck {
void quack();
void fly();
}
// Target 구현체
public class MallardDuck implements Duck {
public void quack() {
System.out.println("Quack");
}
public void fly() {
System.out.println("I am flying");
}
}
// Adaptee Interface - 실제 사용하려는 클래스의 인터페이스
public interface Turkey {
void gobble(); // quack과 다른 메서드명
void fly(); // 짧은 거리만 날 수 있음
}
// Adaptee 구현체
public class WildTurkey implements Turkey {
public void gobble() {
System.out.println("Gobble Gobble");
}
public void fly() {
System.out.println("I'm flying a short distance");
}
}
// Turkey를 Duck처럼 보이게 하는 Adapter
public class TurkeyAdapter implements Duck {
Turkey turkey; // Adaptee를 포함
// 생성자에서 Adaptee 주입
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
// Target 메서드를 Adaptee 메서드로 변환
public void quack() {
turkey.gobble(); // quack() → gobble()
}
// 동작 차이도 보정 가능
public void fly() {
// Turkey는 짧게만 날 수 있으므로 5번 반복
for (int i = 0; i < 5; i++) {
turkey.fly();
}
}
}
public class DuckTestDrive {
public static void main(String[] args) {
// 일반 Duck
Duck duck = new MallardDuck();
// Turkey 객체
Turkey turkey = new WildTurkey();
// Turkey를 Duck으로 Adapt! (핵심!)
Duck turkeyAdapter = new TurkeyAdapter(turkey);
System.out.println("=== Turkey 원본 ===");
turkey.gobble();
turkey.fly();
System.out.println("\n=== 일반 Duck ===");
testDuck(duck);
System.out.println("\n=== Adapter를 통한 Turkey ===");
testDuck(turkeyAdapter); // Turkey를 Duck처럼 사용!
}
// Duck 인터페이스만 받는 메서드
static void testDuck(Duck duck) {
duck.quack();
duck.fly();
}
}
=== Turkey 원본 ===
Gobble Gobble
I'm flying a short distance
=== 일반 Duck ===
Quack
I am flying
=== Adapter를 통한 Turkey ===
Gobble Gobble
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
// Adaptee: 구형 인터페이스
public interface Enumeration<E> {
boolean hasMoreElements();
E nextElement();
}
// Target: 신형 인터페이스
public interface Iterator<E> {
boolean hasNext();
E next();
void remove(); // Enumeration에는 없는 메서드!
}
public class EnumerationIterator<E> implements Iterator<E> {
Enumeration<E> enumeration; // Adaptee
public EnumerationIterator(Enumeration<E> enumeration) {
this.enumeration = enumeration;
}
// 메서드 이름만 변환
public boolean hasNext() {
return enumeration.hasMoreElements();
}
public E next() {
return enumeration.nextElement();
}
// Adaptee가 지원하지 않는 기능!
public void remove() {
throw new UnsupportedOperationException(
"Enumeration doesn't support remove()"
);
}
}
public class IteratorEnumeration<E> implements Enumeration<E> {
Iterator<E> iterator;
public IteratorEnumeration(Iterator<E> iterator) {
this.iterator = iterator;
}
public boolean hasMoreElements() {
return iterator.hasNext();
}
public E nextElement() {
return iterator.next();
}
// remove()는 Enumeration에 없으므로 무시
}
public class AdapterDemo {
public static void main(String[] args) {
// 구형 컬렉션
Vector<String> vector = new Vector<>();
vector.add("Apple");
vector.add("Banana");
vector.add("Cherry");
// Enumeration → Iterator로 Adapt
Enumeration<String> enumeration = vector.elements();
Iterator<String> iterator = new EnumerationIterator<>(enumeration);
// 이제 Iterator 인터페이스로 사용 가능!
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
간단한 변환:
quack() → gobble())public void quack() {
turkey.gobble(); // 단순 위임
}
복잡한 변환:
fly() 5번 반복)public void fly() {
for (int i = 0; i < 5; i++) {
turkey.fly();
}
}
하나의 Adapter가 Target과 Adaptee 양쪽 인터페이스 모두 구현:
// 양방향 Adapter
public class TwoWayAdapter implements Duck, Turkey {
Duck duck;
Turkey turkey;
// Duck으로도, Turkey로도 사용 가능!
public void quack() {
if (turkey != null) turkey.gobble();
else if (duck != null) duck.quack();
}
public void gobble() {
if (duck != null) duck.quack();
else if (turkey != null) turkey.gobble();
}
public void fly() {
if (duck != null) duck.fly();
else if (turkey != null) turkey.fly();
}
}
Adaptee가 Target의 모든 기능을 지원하지 않을 때:
public void remove() {
// 방법 1: 예외 던지기
throw new UnsupportedOperationException();
// 방법 2: 아무것도 안하기 (적절한 경우)
// 방법 3: 기본 동작 제공
}
Adapter Pattern은 한 클래스의 인터페이스를 클라이언트가 기대하는 다른 인터페이스로 변환합니다. 호환되지 않는 인터페이스 때문에 함께 동작할 수 없는 클래스들을 함께 작동하도록 만듭니다.
| 요소 | 역할 | 책임 |
|---|---|---|
| Target | 클라이언트가 사용하는 인터페이스 | 애플리케이션의 요구사항 정의 |
| Adapter | Target을 구현하고 Adaptee를 포함 | 인터페이스 변환 및 호출 위임 |
| Adaptee | 실제 기능을 가진 클래스 | 실제 작업 수행 |
| Client | Target 인터페이스 사용 | 비즈니스 로직 실행 |
| 특성 | Object Adapter | Class Adapter |
|---|---|---|
| 구현 방식 | Composition (Has-A) | Inheritance (Is-A) |
| Java 지원 | ✅ 지원 | ❌ 다중 상속 불가 |
| 유연성 | 높음 (런타임 교체 가능) | 낮음 (컴파일타임 고정) |
| 권장도 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
장점:
단점:
// ✅ Good: 명확한 Adapter 이름
public class LegacyPaymentAdapter implements ModernPaymentGateway
// ❌ Bad: 모호한 이름
public class PaymentConverter
// ✅ Good: 변환 로직만 포함
public void pay(double amount) {
legacySystem.makePayment(amount * 100); // 달러 → 센트 변환
}
// ❌ Bad: 비즈니스 로직 포함
public void pay(double amount) {
if (amount > 1000) sendEmail(); // Adapter 책임 초과!
legacySystem.makePayment(amount * 100);
}
Adapter vs Decorator vs Proxy:
Adapter: 다른 인터페이스로 변환
Decorator: 같은 인터페이스, 기능 추가
Proxy: 같은 인터페이스, 접근 제어