호환되지 않는 인터페이스를 가진 객체들이 협업할 수 있게 해주는 구조 패턴
주식 시장 모니터 앱 가정

한 객체의 인터페이스를 다른 객체가 이해할 수 있게 변환하는 어댑터 사용
어댑터 - 객체 중 하나를 래핑해 복잡한 변환을 숨김, 래핑된 객체는 어댑터의 존재 모름
어댑터는 데이터를 다양한 포맷으로 변환할 수 있을 뿐만 아니라, 다른 인터페이스를 가진 객체들끼리 협업할 수 있게 도와줌
동작 방식:
1. 어댑터는 기존 객체들 중 하나와 호환되는 인터페이스를 받음
2. 기존 객체는 이 인터페이스를 사용해 안전하게 어댑터의 메서드 호출 가능
3. 호출을 받으면 어댑터는 요청을 또 다른 객체가 기대하는 포맷과 순서로 전달

위 문제에서는 코드가 직접적으로 다루는 분석 라이브러리의 모든 클래스에 XML-to-JSON 어댑터를 생성하고, 코드와 라이브러리가 해당 어댑터들을 통해서만 소통하게 해서, 어댑터가 호출을 받으면 들어오는 XML 데이터를 JSON으로 변환해 래핑된 분석 객체의 메서드에게 전달하면 해결
객체 합성 구현 - 한 객체의 인터페이스를 구현하고 다른 객체를 래핑함, 대부분 언어로 구현 가능

1. 클라이언트 - 프로그램의 기존 비즈니스 로직을 가지고 있는 클래스
2. 클라이언트 인터페이스 - 클라이언트 코드와 협업하기 위해 다른 클래스들이 따라야 하는 프로토콜 묘사
3. 서비스 - 인터페이스가 호환되지 않아 클라이언트가 직접 사용할 수 없는 클래스
4. 어댑터 - 클라이언트와 서비스 모두를 다룰 수 있는 클래스
- 클라이언트 인터페이스를 구현하고 서비스 객체를 래핑함
- 어댑터 인터페이스를 통해 클라이언트로부터 호출을 받고,
래핑된 서비스 객체가 이해할 수 있는 포맷을 가진 호출로 변환
5. 클라이언트 코드는 클라이언트 인터페이스를 통해 어댑터를 다루는 한 concrete 어댑터 클래스와 결합되지 않음
- 이 덕분에 기존 클라이언트 코드를 훼손하지 않고도 새로운 유형의 어댑터를 도입할 수 있음
- 서비스의 인터페이스가 변경되거나 대체되는 상황에서 유용
상속 구현 - 어댑터가 두 객체의 인터페이스를 동시에 상속, 다중 상속을 지원하는 C++같은 언어에서만 구현 가능

1. 클래스 어댑터 - 클라이언트와 서비스 모두의 행위를 상속받기 때문에 어떤 객체도 래핑할 필요가 없음
- 어댑테이션은 오버라이드된 메서드에서 발생
- 어댑터는 기존 클라이언트 클래스 대신 사용 가능
- 어댑터 패턴은 레거시 클래스, 써드 파티 클래스 등 다른 인터페이스를 가진 클래스와 코드 사이의
통역사 역할을 하는 중간 레이어 클래스를 생성하게 해줌
- 각각의 서브클래스를 확장해 새로운 자식 클래스들을 생성해 누락된 기능을 넣어줄 수도 있지만,
이 경우에는 새로운 코드가 새로운 클래스들 전체에 중복됨
- 더 좋은 해결책은 누락된 기능을 어댑터 클래스에 넣고,
객체들을 어댑터로 감싸서 필요한 기능을 동적으로 얻을 수 있게 해주는 것
- 대상 클래스들에게 공통된 인터페이스가 있고, 어댑터의 필드가 그 인터페이스를 따라야 함
→ 데코레이터 패턴과 유사
1. 호환되지 않는 인터페이스를 가진 두 개 이상의 클래스가 있어야 함
- 변경이 불가능한, 유용한 서비스 클래스
- 서비스 클래스를 사용해 이득을 볼 수 있는 클라이언트 클래스
2. 클라이언트 인터페이스를 선언하고 클라이언트가 어떻게 서비스와 소통해야 하는지 묘사
3. 어댑터 클래스를 생성하고 클라이언트 인터페이스를 따르게 함, 메서드들은 우선 비어있는 상태로 둠
4. 어댑터 클래스에 서비스 객체의 참조를 저장할 필드 추가
- 보통 생성자로 이 필드를 초기화하지만, 가끔은 메서드 호출 시 어댑터에 전달해주는 게 편할 때도 있음
5. 클라이언트의 모든 메서드들을 어댑터 클래스에 구현
- 어댑터는 대부분의 실제 작업을 서비스 객체에 위임하고, 인터페이스나 데이터 변환만 해야 함
6. 클라이언트는 클라이언트 인터페이스를 통해 어댑터 사용
→ 클라이언트 코드에 영향을 주지 않고 어댑터 변경/확장 가능
- SRP - 인터페이스나 데이터 변환 코드를 프로그램의 주요 비즈니스 로직과 분리 가능
- OCP - 클라이언트가 클라이언트 인터페이스를 통해 어댑터를 사용한다면 기존 코드를 훼손하지 않고 새로운 유형의 어댑터 도입 가능
- 새로운 인터페이스들과 클래스들을 도입해야 해서 코드 복잡도 증가, 때로는 서비스 클래스를 코드에 맞게 변경하는 게 더 간단할 수도 있음
- 브리지 - 보통 사전에 설계되어 프로그램의 각 부분들을 독립적으로 개발 가능하게 함
어댑터 - 대개 기존 프로그램과 함께 사용돼 원래 호환되지 않는 클래스가 잘 동작하도록 만듦
- 어댑터 - 기존 객체의 인터페이스 변경
데코레이터 - 인터페이스를 변경하지 않고 객체 향상, 재귀 합성 지원
- 어댑터 - 래핑된 객체에 다른 인터페이스 제공
프록시 - 같은 인터페이스 제공
데코레이터 - 향상된 인터페이스 제공
- 퍼사드 - 기존 객체에 새로운 인터페이스 정의, 객체들의 전체적인 하위시스템과 동작
어댑터 - 기존 인터페이스를 사용 가능하게 만듦, 대개 한 객체만 래핑함
- 브리지, 파사드, 전략, 어댑터 - 다른 객체에 작업을 위임하는 합성 기반 패턴이라는 점에서 유사하지만
모두 다른 문제를 해결함
/**
* The Target defines the domain-specific interface used by the client code.
*/
class Target {
public request(): string {
return 'Target: The default target\'s behavior.';
}
}
/**
* The Adaptee contains some useful behavior, but its interface is incompatible
* with the existing client code. The Adaptee needs some adaptation before the
* client code can use it.
*/
class Adaptee {
public specificRequest(): string {
return '.eetpadA eht fo roivaheb laicepS';
}
}
/**
* The Adapter makes the Adaptee's interface compatible with the Target's
* interface.
*/
class Adapter extends Target {
private adaptee: Adaptee;
constructor(adaptee: Adaptee) {
super();
this.adaptee = adaptee;
}
public request(): string {
const result = this.adaptee.specificRequest().split('').reverse().join('');
return `Adapter: (TRANSLATED) ${result}`;
}
}
/**
* The client code supports all classes that follow the Target interface.
*/
function clientCode(target: Target) {
console.log(target.request());
}
console.log('Client: I can work just fine with the Target objects:');
const target = new Target();
clientCode(target);
console.log('');
const adaptee = new Adaptee();
console.log('Client: The Adaptee class has a weird interface. See, I don\'t understand it:');
console.log(`Adaptee: ${adaptee.specificRequest()}`);
console.log('');
console.log('Client: But I can work with it via the Adapter:');
const adapter = new Adapter(adaptee);
clientCode(adapter);
// Output.txt
Client: I can work just fine with the Target objects:
Target: The default target's behavior.
Client: The Adaptee class has a weird interface. See, I don't understand it:
Adaptee: .eetpadA eht fo roivaheb laicepS
Client: But I can work with it via the Adapter:
Adapter: (TRANSLATED) Special behavior of the Adaptee.
참고 자료: Refactoring.guru