디자인 패턴

Adapter 패턴

Keisa 2024. 6. 29. 14:10

 

문제

주식 시장 모니터링 앱을 만들고 있고, 이 앱은 여러 소스에서 주식 데이터를 XML 형식으로 다운로드한 후 사용자에게 보기 좋은 차트들과 다이어그램들을 표시한다고 상상해 봅시다.

어느 시점에 당신은 타사의 스마트 분석 라이브러리를 통합하여 당신의 앱을 개선하기로 결정했습니다. 그런데 함정이 있습니다: 이 분석 라이브러리는 JSON 형식의 데이터로만 작동한다는 것입니다.

위 분석 라이브러리는 '있는 그대로' 사용할 수 없습니다. 왜냐하면 당신의 앱과 호환되지 않는 형식의 데이터를 기다리고 있기 때문입니다.

당신은 이 라이브러리를 XML과 작동하도록 변경할 수 있으나, 그러면 라이브러리에 의존하는 일부 기존 코드가 손상될 수 있습니다. 또 처음부터 타사의 라이브러리 소스 코드에 접근하는 것이 불가능하여 위의 해결 방식을 사용하지 못할 수도 있습니다.

 해결책

당신은 를 만들 수 있습니다. 어댑터는 한 객체의 인터페이스를 다른 객체가 이해할 수 있도록 변환하는 특별한 객체입니다.

어댑터는 변환의 복잡성을 숨기기 위하여 객체 중 하나를 래핑​(포장)​합니다. 래핑된 객체는 어댑터를 인식하지도 못합니다. 예를 들어 미터 및 킬로미터 단위로 작동하는 객체를 모든 데이터를 피트 및 마일과 같은 영국식 단위로 변환하는 어댑터로 래핑할 수 있습니다.

어댑터는 데이터를 다양한 형식으로 변환할 수 있을 뿐만 아니라 다른 인터페이스를 가진 객체들이 협업하는 데에도 도움을 줄 수 있으며, 대략 다음과 같이 작동합니다:

  1. 어댑터는 기존에 있던 객체 중 하나와 호환되는 인터페이스를 받습니다.
  2. 이 인터페이스를 사용하면 기존 객체는 어댑터의 메서드들을 안전하게 호출할 수 있습니다.
  3. 호출을 수신하면 어댑터는 이 요청을 두 번째 객체에 해당 객체가 예상하는 형식과 순서대로 전달합니다.

때로는 양방향으로 호출을 변환할 수 있는 양방향 어댑터를 만드는 것도 가능합니다.

다시 당신의 주식 시장 앱을 살펴봅시다. 당신은 형식이 호환되지 않는 문제를 해결하기 위해 당신의 코드와 직접 작동하는 분석 라이브러리의 모든 클래스에 대한 XML->JSON 변환 어댑터를 만듭니다. 그 후 이러한 어댑터들을 통해서만 해당 라이브러리와 통신하도록 코드를 조정합니다. 어댑터는 호출을 받으면 들어오는 XML 데이터를 JSON 구조로 변환한 후 해당 호출을 래핑된 분석 객체의 적절한 메서드들에 전달합니다.

 실제상황 적용

해외여행 전과 후의 서류가방.

미국에서 유럽으로 처음 여행을 가서 노트북을 충전하려고 하면 깜짝 놀랄지도 모릅니다. 전원 플러그와 소켓은 국가마다 표준이 달라 미국 플러그가 독일 소켓에 맞지 않을 수 있기 때문입니다. 이 문제는 미국식 소켓과 유럽식 플러그가 있는 전원 플러그 어댑터를 사용하면 해결할 수 있습니다.

 구조

객체 어댑터

이 구현은 객체 합성 원칙을 사용합니다. 어댑터는 한 객체의 인터페이스를 구현하고 다른 객체는 래핑합니다. 위 합성은 모든 인기 있는 프로그래밍 언어로 구현할 수 있습니다.

  1. 클라이언트는 프로그램의 기존 비즈니스 로직을 포함하는 클래스입니다.
  2. 클라이언트 인터페이스는 다른 클래스들이 클라이언트 코드와 공동 작업할 수 있도록 따라야 하는 프로토콜을 뜻합니다.
  3. 서비스는 일반적으로 타사 또는 레거시의 유용한 클래스를 뜻합니다. 클라이언트는 서비스 클래스를 직접 사용할 수 없습니다. 왜냐하면 서비스 클래스는 호환되지 않는 인터페이스를 가지고 있기 때문입니다.
  4. 어댑터는 클라이언트와 서비스 양쪽에서 작동할 수 있는 클래스로, 서비스 객체를 래핑하는 동안 클라이언트 인터페이스를 구현합니다. 어댑터는 어댑터 인터페이스를 통해 클라이언트로부터 호출들을 수신한 후 이 호출을 래핑된 서비스 객체가 이해할 수 있는 형식의 호출들로 변환합니다.
  5. 클라이언트 코드는 클라이언트 인터페이스를 통해 어댑터와 작동하는 한 구상 어댑터 클래스와 결합하지 않습니다. 덕분에 기존 클라이언트 코드를 손상하지 않고 새로운 유형의 어댑터들을 프로그램에 도입할 수 있습니다. 이것은 서비스 클래스의 인터페이스가 변경되거나 교체될 때 유용할 수 있습니다: 클라이언트 코드를 변경하지 않은 채 새 어댑터 클래스를 생성할 수 있으니까요.

클래스 어댑터

이 구현은 상속을 사용하며, 어댑터는 동시에 두 객체의 인터페이스를 상속합니다. 이 방식은 C++ 와 같이 다중 상속을 지원하는 프로그래밍 언어에서만 구현할 수 있습니다.

 

어댑터 클래스가 생성될 부분은 위의 예시를 기준으로 XML을 이용하느냐 JSON을 이용하느냐에 따라 XML->JSON이라면 XML을 생성하는 클래스의 끝단, JSON->XML이라면 JSON을 생성하는 클래스의 끝단이 된다. 

생성위치를 정하고나면 Interface 클래스를 통해 생성된 클래스에서 XML 혹은 JSON 등의 원하는 형식을 넘겨줄 수 있도록 자원 변환을 수행하고 던질 수 있어야한다. 

클래스와 클래스간, 특히 라이브러리와 기존 시스템간의 연결을 수행할 때 많이 이용할 수 있어보이며 네트워크에서도 송수신 패킷의 내용을 변환할 때에 이용할 수 있을 것으로 보인다.

/**
 * The Target defines the domain-specific interface used by the client code.
 */
class Target {
 public:
  virtual ~Target() = default;

  virtual std::string Request() const {
    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:
  std::string SpecificRequest() const {
    return ".eetpadA eht fo roivaheb laicepS";
  }
};

/**
 * The Adapter makes the Adaptee's interface compatible with the Target's
 * interface.
 */
class Adapter : public Target {
 private:
  Adaptee *adaptee_;

 public:
  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;
  }
};

/**
 * The client code supports all classes that follow the Target interface.
 */
void ClientCode(const Target *target) {
  std::cout << target->Request();
}

int main() {
  std::cout << "Client: I can work just fine with the Target objects:\n";
  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;
}

 

참조: https://ansohxxn.github.io/design%20pattern/chapter13/

 

Chapter 13. 어댑터 패턴(Adapter Pattern)

인프런에 있는 이재환님의 강의 게임 디자인 패턴 with Unity 를 듣고 정리한 필기입니다. 😀

ansohxxn.github.io