1. Adapter
- 한 클래스의 인터페이스를 클라이언트에서 사용하고자 하는 다른 인터페이스로 변환
- 어댑터를 이용하면 인터페이스 호환성 문제로 같이 쓸 수 없는 클래스들을 연결해서 사용 가능
- 호환되지 않는 인터페이스를 사용하는 클라이언트를 그대로 활용 가능
- 예를 들어 전기 콘센트를 보면 한국의 플러그를 일본 소켓에 끼우려면 어댑터를 사용해야 함
- 레거시 코드를 기반으로 하는 시스템에서 자주 사용
1.1. 의도
- 호환되지 않는 인터페이스를 가진 객체들이 협업할 수 있도록 하는 구조적 디자인 패턴
1.2. 문제
- 모니터링 앱을 만들고 있고, 이 앱은 여러 소스에서 데이터를 XML 형식으로 다운로드 한 후,
- 사용자에게 보기 좋은 차트들과 다이어그램들을 제공한다고 가정
- 타사의 분석 라이브러리를 통합하여 앱을 개선하기로 했는데 해당 라이브러리가 JSON 형식의 데이터로만 작동하는 경우
- 라이브러리를 XML과 작동하도록 변경할 수 있으나, 그러면 라이브러리에 의존하는 일부 기존 코드가 손상될 수 있음
1.3. 해결책
- 어댑터 패턴을 이용하여 해결할 수 있음
- 어댑터는 한 객체의 인터페이스를 다른 객체가 이용할 수 있도록 변환하는 객체
- 어댑터를 만드는 과정에서 복잡성을 피하기 위해 클래스 중 하나를 wrapping 함
- 데이터를 다양한 형식으로 변환할 수 있을 뿐만 아니라 다른 인터페이스를 가진 객체들이 협업하는 데에도 도움을 줄 수 있음
- 동작 과정
- 어댑터는 기존에 있던 객체 중 하나와 호환되는 인터페이스를 받음
- 이 인터페이스를 사용하면 기존 객체는 어댑터의 메소드들을 안전하게 호출 가능
- 호출을 수신하면 어댑터는 이 요청을 두 번째 객체에 해당 객체가 예상하는 형식과 순서대로 전달 가능
2. 구조
2.1. Object Adapter
- Object composition principle을 사용
- 한 객체의 인터페이스로 구현하고 다른 객체는 wrapping 함
- Client
- 프로그램의 기존 비즈니스 로직을 포함하는 클래스
- Client Interface
- 다른 클래스들이 클라이언트 코드와 공동 작업할 수 있도록 따라야 하는 인터페이스
- Service
- 3rd party or Legacy의 사용중인 클래스
- 클라이언트가 이를 직접 사용하는 것은 불가 (왜냐하면 서비스 class는 호환되지 않는 인터페이스를 가지기 때문에)
- Adapter
- 클라이언트와 서비스 양쪽에서 작동할 수 있도록 하는 class
- 서비스 객체를 wrapping 하면서 클라이언트 인터페이스를 기반으로 구현
- 클라이언트로부터 호출을 수신한 후 이 호출을 wrapping된 서비스 객체가 이해할 수 있는 형식의 호출로 변환
2.2. Class Adapter
- 이 구현은 상속을 사용하며, Adapter는 동시에 두 객체의 인터페이스를 상속
- 이 방식은 C++와 같이 다중 상속을 지원하는 프로그래밍 언어에서만 구현 가능
- 클래스 어댑터는 객체를 wrapping할 필요가 없음(이유는 클라이언트와 서비스 양쪽에서 상속 받기 때문에)
3. 사용
- 기존 클래스를 사용하고 싶지만 인터페이스가 나머지 코드와 호환되지 않을 때 사용
- 이 패턴은 legacy 코드와 3rd party 클래스 또는 special한 인터페이스가 있는 다른 클래스 간의 변환기 역할을 하는 중간 layer
- 부모 클래스에 추가할 수 없는 어떤 공통 기능들을 가지지 않은 여러 legacy 서브클래스들이 재사용하려는 경우에 사용
- 각 서브클래스를 확장한 후 누락된 기능들을 새로운 서브클래스들에 넣을 수 있지만 해당 코드를 모든 새로운 클래스들에 복제해야 되지 때문에 smell 코드임
- 해결
- 깔끔한 해결책은 누락된 기능을 어댑터 클래스에 넣는 것
- 어댑터 내부에 누락된 기능이 있는 개체들을 wrapping 하면 필요한 기능들을 얻을 수 있음
- 이 해결책은 대상 클래스들에 반드시 공통 인터페이스가 있어야 하며 어댑터의 field는 해당 인터페이스를 따라야 함 ( Decorator 패턴과 매우 유사 )
4. Pros and Cons
4.1. Pros
- Single Responsibility Principle, 프로그램의 기존 비즈니스 로직에서 인터페이스 또는 데이터 변환 코드를 분리할 수 있음
- Open/Closed Principle, 클라이언트 코드가 클라이언트 인터페이스를 통해 어댑터와 동작하면 기존의 클라이언트 코드를 손상하지 않고 새로운 코드를 도입할 수 있음
4.2. Cons
- Code complexity, 다수의 새로운 인터페이스와 클래스들을 도입해야 하므로 코드의 복잡성이 증가
- 코드의 남은 부분과 작동하도록 서비스 클래스를 변경하는 편이 효율적일 수 있음
2. 코드로 알아보기
Object Adapter
#include <iostream>
#include <string>
#include <algorithm>
class Target {
public:
virtual ~Target() = default;
virtual std::string Request() const {
return "Target: The default target's behavior.";
}
};
class Adaptee {
public:
std::string SpecificRequest() const {
return ".eetpadA eht fo roivaheb laicepS";
}
};
// Adapter 클래스
class Adapter : public Target {
public:
// 생성자에 Adaptee를 받아서 넘김
// 경우에 따라 다양한 Adaptee를 만들어서 Adapter에 적용할 수 있음
Adapter(Adaptee *adaptee) : adaptee_(adaptee) {}
std::string Request() const override {
std::string to_reverse = this->adaptee_->SpecificRequest();
std::reverse(to_reverse.begin(), to_reverse.end());
return "Adapter: (TRANSLATED) " + to_reverse;
}
private:
Adaptee *adaptee_;
};
// 클라이언트
void ClientCode(const Target *target) {
std::cout << target->Request();
}
int main() {
// 레거시 코드라고 한다면
Target *target = new Target;
ClientCode(target);
std::cout << "\n\n";
// 새로 추가된 기능인데 문장이 반대로 써 있는 경우
Adaptee *adaptee = new Adaptee;
std::cout << "Client: The Adaptee class has a weird interface. See, I don't understand it:\n";
std::cout << "Adaptee: " << adaptee->SpecificRequest();
std::cout << "\n\n";
// 어댑터를 적용하여 올바른 문장으로 만듬
std::cout << "Client: But I can work with it via the Adapter:\n";
Adapter *adapter = new Adapter(adaptee);
ClientCode(adapter);
std::cout << "\n";
delete target;
delete adaptee;
delete adapter;
return 0;
}
Class adapter ( 다중상속 )
#include <iostream>
#include <string>
#include <algorithm>
class Target {
public:
virtual ~Target() = default;
virtual std::string Request() const {
return "Target: The default target's behavior.";
}
};
class Adaptee {
public:
std::string SpecificRequest() const {
return ".eetpadA eht fo roivaheb laicepS";
}
};
// Adapter 클래스가 다중 상속인 경우
class Adapter : public Target, public Adaptee {
public:
Adapter() {}
std::string Request() const override {
std::string to_reverse = SpecificRequest();
std::reverse(to_reverse.begin(), to_reverse.end());
return "Adapter: (TRANSLATED) " + to_reverse;
}
};
// 클라이언트
void ClientCode(const Target *target) {
std::cout << target->Request();
}
int main() {
// 레거시 코드라고 한다면
Target *target = new Target;
ClientCode(target);
std::cout << "\n\n";
// 새로 추가된 기능인데 문장이 반대로 써 있는 경우
Adaptee *adaptee = new Adaptee;
std::cout << "Client: The Adaptee class has a weird interface. See, I don't understand it:\n";
std::cout << "Adaptee: " << adaptee->SpecificRequest();
std::cout << "\n\n";
// 어댑터를 적용하여 올바른 문장으로 만듬
std::cout << "Client: But I can work with it via the Adapter:\n";
Adapter *adapter = new Adapter;
ClientCode(adapter);
std::cout << "\n";
delete target;
delete adaptee;
delete adapter;
return 0;
}
참고
wikipedia
refactoring.guru
This is personal diary for study documents.
Please comment if I'm wrong or missing something else 😄.
Top
댓글남기기