클래스의 인터페이스를 사용자가 기대하는 인터페이스 형태로 적응(변환) 시킵니다. 서로 일치하지 않는 인터페이스를 갖는 클래스들을 함께 동작시킵니다. - GoF
클래스의 인터페이스를 사용자가 원하는, 사용하고자 하는 다른 인터페이스로 변환하는 패턴으로, 서로 호환성이 전혀 없는 인터페이스를 사용하는 클래스들이 상호 호환되게 하여 함께 동작할 수 있도록 해준다.
* Wrapper 패턴이라고도 부른다.

| Target : 클라이언트가 사용하길 원하는 인터페이스 |
| Adaptee : 클라이언트가 갖고 있는 인터페이스 (어댑터에서 사용하고자 하는 인터페이스) |
| Adapter : Target 인터페이스를 구현하는 클래스로, 이 때 Adaptee의 함수 사용 |
| Client : Target 인터페이스를 사용하는 (사용하고 싶어하는) 주체 |
예를 들어, 한국의 표준 플러그를 일본에 전원 소켓에 바로 끼워줄 수 없어 동그란 모양을 일자로 바꿔주는 어댑터를 끼워주어야 한다. 여기서 어댑터는 소켓 인터페이스를 플러그에서 필요로 하는 인터페이스로 바꿔준다고 할 수 있다.

interface Target
public interface Target {
// 클라이언트가 사용하려는 인터페이스
void operation();
}
public class SampleAdapter implements Target{
// Adapter는 Adaptee를 감싸고 있으며, Target 인터페이스를 구현한다.
private final Adaptee adaptee;
public SampleAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void operation() {
this.adaptee.specificOperation();
}
}interface Adaptee
public interface Adaptee {
void specificOperation();
}
public class SampleAdaptee implements Adaptee {
// Adaptee 는 이미 개발이 완료되었고, 수정이 곤란한 상황
@Override
public void specificOperation() {
System.out.println(" This is Adapter ! ");
}
}
Main
public class Main {
public static void main(String[] args) {
Adaptee adaptee = new SampleAdaptee();
Target target = new SampleAdapter(adaptee);
target.operation();
}
}
Interface Duck
public interface Duck {
void quack();
void fly();
}
public class MallarDuck implements Duck{
@Override
public void quack() {
System.out.println("Quack");
}
@Override
public void fly() {
System.out.println("I'm flying");
}
}
Interface Turkey
public interface Turkey {
void gobble();
void fly();
}
public class WildTurkey implements Turkey {
@Override
public void gobble() {
System.out.println("Gobble gobble");
}
@Override
public void fly() {
System.out.println("I'm flying a short distance");
}
}
Class TurkeyAdapter
public class TurkeyAdapter implements Duck{
private Turkey turkey;
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
@Override
public void quack() {
turkey.gobble();
}
@Override
public void fly() {
for (int i = 0; i < 5; i++) {
turkey.fly();
}
}
}
Main
public class Main {
public static void main(String[] args) {
MallarDuck mallarDuck = new MallarDuck();
Turkey turkey = new WildTurkey();
Duck duck = new TurkeyAdapter(turkey);
duck.quack();
duck.fly();
}
}
클라이언트에서 어댑터 사용
- 클라이어트에서 타겟 인터페이스를 사용하여 메소드를 호출함으로써 어댑터에 요청한다.
- 어댑터에서는 어댑터 인터페이스를 사용하여 그 요청을 어댑티에 대한 하나 이상의 메소드를 호출로 변환한다.
- 클라이언트에서는 호출결과를 받긴 하지만 중간에 어댑터가 껴 있는지는 전혀 알지 못한다.


클래스 어댑터에서는 어댑터를 만들 때 타겟과 얻ㅂ티 모두의 서브 캘르스로 만들고, 객체 어댑터에서는 구성을 통해서 어댑티에 요청을 전달한다는 점을 제외하면 별다른 차이점이 없다.
Enumeration
Enumeration을 리턴하는 elements() 메서드가 구현되어 있었던 초기 컬렉션 클래스(Vector ,Stack, Hashtable 등)
Enumeration 인터페이스를 이용하면 컬렉션 내에서 각 항목이 관리되는 방식에는 신경 쓸 필요 없이 컬렉션의 모든 항목에 접근이 가능하다.
Iterator
JDK 버전이 올라가며 Iterator라는 새로운 컬렉션 클래스가 추가되었다.
*기존 사용하던 Enumeration 인터페이스를 사용하여 작성된 코드와 상호 작동해야하므로 JAVA의 익명 내부 클래스 기능을 이용해 Iterator 를 어댑팅하는 생성 메서드를 제공한다.
| 어댑터 패턴은 합성을 사용한 기법과 상속을 사용하는 기법이 있다. *객체 어댑터, 클래스 어댑터 위 내용은 모두 합성을 사용한 방식이었다. 그러나 아래의 방법은 상속을 사용하는 방법이다. 대부분의 경우 상속이 바람직하지 않은 해결책일 수 있다. |

class WrappedObjectIterator
import java.io.ObjectInputStream;
import java.util.Iterator;
public class WrappedObjectIterator implements Iterator {
private boolean atEndOfFile = false;
private final ObjectInputStream in; // adaptee를 갖고 있다.(합성)
public WrappedObjectIterator(ObjectInputStream in) {
this.in = in;
}
@Override
public boolean hasNext() {
return atEndOfFile == false;
}
@Override
public Object next() {
// Adaptee의 readOject()를 Iterator.next로 감싼다.
try {
return in.readObject();
} catch (Exception e) {
atEndOfFile = true;
return null;
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
class ObjectIterator
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.util.Iterator;
public class ObjectIterator extends ObjectInputStream implements Iterator {
private boolean atEndOfFile = false;
public ObjectIterator(InputStream in) throws IOException {
super(in);
}
@Override
public boolean hasNext() {
return atEndOfFile == false;
}
@Override
public Object next() {
try {
return readObject(); // super의 readObject()를 호출한다.
} catch (Exception e) {
atEndOfFile = true;
return null;
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
Class Adapter
장점 :
- 어댑터 전체를 다시 구현할 필요가 없어 빠르다.
단점 :
- 상속을 활용하기 때문에, 유연하지 못하다.
Object Adapter
장점 :
- 구성(Composition)을 사용하기 때문에 더욱 유연하다.
단점 :
- 대부분 코드를 구현해야 하기 때문에 비효율적일 수 있다.
*어댑터 패턴은 변경할 수 없는 내부 구현, 라이브러리 등에 추가적인 기능을 만들고 싶을 때 유용하게 활용할 수 있다.
특히, 기존에 사용하던 라이브러리의 동작을 바꿔야 하는 상황에서, 라이브러리의 구현을 바꾸는 것은 위험하다.
따라서, 어댑터 패턴을 활용하여 기존 코드를 건드리지 않고 새로운 동작을 구현하면 훨씬 안정적이고 재활용성이 우수하다. 또한 폭넓은 확장 가능성을 고려해볼 수 있다.
어댑터 패턴을 구현하기 위해 필요한 구성요소들로 인해 클래스가 많아지므로 복잡도가 증가할 수 있다.
참고 :
https://johngrib.github.io/wiki/pattern/adapter/
어댑터 패턴 (Adapter Pattern)
서로 일치하지 않는 인터페이스를 가진 클래스를 함께 동작시킨다
johngrib.github.io
https://jusungpark.tistory.com/22
디자인패턴 - 어댑터 패턴 (adapter pattern)
어댑터 패턴 (adapter pattern) 한 클래스의 인터페이스를 클라이언트에서 사용하고자하는 다른 인터페이스로 변환한다.어댑터를 이용하면 인터페이스 호환성 문제 때문에 같이 쓸 수 없는 클래스
jusungpark.tistory.com
https://wellsw.tistory.com/240
[디자인패턴][Adapter] 어댑터 패턴
어댑터 패턴 정의 호환성이 없는 기존 클래스의 인터페이스를 변환하여 사용자가 기대하는 인터페이스 형태로 변환시키는 패턴 코드의 재활용성을 증가하고 기존의 코드를 수정하지 않는 장점
wellsw.tistory.com
우리는 이미 '어댑터 패턴'을 알고 있다
실생활로부터 Adapter Pattern 이해하기
velog.io
| [Design Pattern] Proxy Pattern : Virtual Proxy (0) | 2023.03.30 |
|---|---|
| [Design Pattern] Proxy Pattern (0) | 2023.03.29 |
| [Design Pattern] Composite Pattern (0) | 2023.03.28 |
| [Design Pattern] State Pattern (0) | 2023.03.26 |
| [Design Pattern] Builder Pattern (0) | 2023.03.25 |