의도

  • 클래스의 인터페이스를 사용자가 기대하는 인터페이스 형태로 적응시킴. 서로 일치하지 않는 인터페이스를 갖는 클래스들을 함께 동작시킴.

 

다른 이름

  • 래퍼(Wrapper)

 

언제 쓰는가?

  • 기존 클래스를 사용하고 싶은데 인터페이스가 맞지 않을 때.
  • 아직 예측하지 못한 클래스나 실제 관련되지 않는 클래스들이 기존 클래스를 재사용하고자 하지만, 이미 정의된 재사용 가능한 클래스가 지금 요청하는 인터페이스를 꼭 정의하고 있지 않을 때. 다시 말해, 이미 만든 것을 재사용하고자 하나 이 재사용 가능한 라이브러리를 수정할 수 없을 때.
  • [객체 적응자(object adapter)만 해당됨] 이미 존재하는 여러 개의 서브클래스를 사용해야 하는데, 이 서브클래스들의 상속을 통해서 이들의 인터페이스를 다 개조한다는 것이 현실성이 없을 때. 객체 적응자를 써서 부모 클래스의 인터페이스를 변형하는 것이 더 바람직함.

 

구조

GoF의 디자인 패턴 중 P.199
GoF의 디자인 패턴 중 P.199

  • Target : 사용자가 사용할 응용 분야에 종속적인 인터페이스를 정의하는 클래스.
  • Client : Target 인터페이스를 만족하는 객체와 동작할 대상.
  • Adaptee : 인터페이스의 적응이 필요한 기존 인터페이스를 정의하는 클래스로서, 적응 대상자라고 함.
  • Adapter : Target 인터페이스에 Adaptee의 인터페이스를 적응시키는 클래스.

 

협력 방법

  • 사용자는 적응자에 해당하는 클래스의 인스턴스에게 연산을 호출, 적응자는 해당 요청을 수행하기 위해 적응대상자의 연산을 호출함.

 

결과

1. 클래스 적응자 vs 객체 적응자

  • Adapter를 만들기 위해 Adaptee 클래스를 상속 받았을 때와 객체 합성을 통해 만들었을 때 장단점이 있음.
    • 상속 받았을 경우, 특정 서브 클래스에 종속되므로 다른 서브 클래스에 접근하기가 어려워지나 함수의 재사용이나 오버라이딩을 통한 재구현이 가능해짐.
    • 객체 합성을 통해 만들 경우, Adapter 클래스가 하나만 존재해도 수많은 Adaptee 클래스들과 동작할 수 있음. 대신 Adaptee 클래스들의 행동을 재정의하기가 어려워짐.

2.추가 고려 사항

  1. Adapter 클래스가 실제 적응 작업을 위해 들어가는 품이 얼마나 되나 ? : 적응 어떻게 시키는지에 따라 달라짐.시그니처가 다른 인터페이스를 적응시키는 것일수도, 단순히 연산의 이름을 바꾸는 것일 수도 있음. 작업량을 결정짓는 것은 Target 인터페이스와 Adaptee 간이 얼마만큼의 유사성을 갖는가 하는 부분.
  2. 대체 가능 적응자 : 클래스의 재사용성을 높이려면, 누가 이 클래스를 사용할지에 대한 생각을 최소화해야 함. 만약, 인터페이스의 변경이 필요하다면 이 내용을 담은 클래스를 만들면 됨. 표준화 된 인터페이스를 정의해야 한다는 부담을 덜 수 있음. 이런 인터페이스 개조를 담당하는 클래스를 대체 가능 적응자 라고 함.
  3. 양방향 적응자를 통한 투명성 제공 : 적응자의 잠재적인 문제는 적응자가 모든 사용자에게 투명하지 않다는 점. 적응된 객체는 Adaptee 인터페이스를 만족하지 않음. Adaptee 객체를 통해 Target을 사용해야 하는 사용자라면 적응된 객체를 사용할 수 없음. 다중 상속을 이용하여 양쪽 모두에게 맞는 연산을 제공할 수 있음.

 

구현

  1. 클래스 적응자 사용 시 : Adapter 클래스는 Target 클래스에서 public으로 상속받고, Adaptee는 private으로 상속받아야 함. Adaptee는 내부 구현에 필요한 것으로, Adaptee가 사용자에 알려질 필요 없음.
  2. 대체 가능 적응자
    1. 추상 연산 사용 : Adaptee의 가상 함수를 재정의하여 다양한 행동 정의 가능.
    2. 위임 객체 사용 : 위임 객체를 사용하여 다양한 전략 구사. 위임 전용 추상 클래스를 만들어 다양한 객체로 분화 가능.
    3. 매개변수화된 적응자 사용

 

예제 코드 ( 1 )

  • 클래스 적응자의 예부터 먼저 확인해 보겠습니다.
  • 마땅한 예시가 생각나지 않아 아래처럼 구성하였는데 이 패턴을 사용함의 본질은 객체의 인터페이스를 변경하는 것입니다. Destroy와 Sop 간의 연관성이 없다고 생각될 수도 있으나 그 점 참고하고 봐주시면 되겠습니다.
  • 화장실 전용 휴지가 물에 젖으면 잘 파괴된다 라는 가정을 가집니다.
  • 화장실 전용 휴지를 만드는 과정에서 휴지가 젖은 경우에 파괴된다라는 속성을 추가한 경우입니다.
#include "Adapter1.h"

int main()
{
	ToiletTissue t_tissue;
	t_tissue.Sop();

	return 0;
}

  • 화장실 휴지는 물에 젖었고, 파괴됨을 알립니다.
//Adapter1.h

#pragma once
#include <iostream>

class Tissue
{
public:
	virtual void Sop()
	{
		std::cout << "Tissue가 물에 젖었습니다." << std::endl;
	}
};

class Destructible
{
public:
	void Destroy()
	{
		std::cout << "이 물체를 파괴합니다." << std::endl;
	}
};

class ToiletTissue : public Tissue, private Destructible
{
public:
	virtual void Sop() override
	{
		Tissue::Sop();
		Destroy();
	}
};
  • 휴지가 물에 젖었음을 호출할 수 있게 Tissue는 public 상속, 파괴될 수 있는 속성은 내부에서만 사용할 것이기 때문에 private 상속으로 해주었습니다.
  • 물에 젖는 순간 젖었음을 화면에 출력하고, 파괴됨도 화면에 출력합니다.

 

예제 코드 ( 2 )

  • 위 예제 내용은 그대로 객체 적응자로 진행하겠습니다.
  • 객체 적응자로 이 패턴을 사용하였을 경우의 장점인 다양한 서브클래스로 갈아낄 수 있다는 점에 중점을 두어 예제 작성했습니다.
#include "Adapter2.h"

int main()
{
	ToiletTissue_V2 t_tissue1;
	ToiletTissue_V2 t_tissue2;
	Destructible_V2 destructible;
	SoftDestructible soft_destructible;

	t_tissue1.SetDestructible(&destructible);
	t_tissue2.SetDestructible(&soft_destructible);

	t_tissue1.Sop();
	t_tissue2.Sop();

	return 0;
}

  • tissue1은 단순히 파괴되었고, tissue2는 부드럽게 파괴되었습니다. 어떤 destructible로 객체 합성을 했는지에 따라 다른 결과입니다.
#pragma once
#include <iostream>

class Tissue_V2
{
public:
	virtual void Sop()
	{
		std::cout << "Tissue가 물에 젖었습니다." << std::endl;
	}
};

class Destructible_V2
{
public:
	virtual void Destroy()
	{
		std::cout << "이 물체를 파괴합니다." << std::endl;
	}
};

class SoftDestructible : public Destructible_V2
{
public:
	virtual void Destroy() override
	{
		std::cout << "부드럽게 ";
		__super::Destroy();
	}
};

class ToiletTissue_V2 : public Tissue_V2
{
public:
	virtual void Sop() override
	{
		Tissue_V2::Sop();
		destructible->Destroy();
	}
	
	void SetDestructible(Destructible_V2* destructible)
	{
		this->destructible = destructible;
	}

private:
	Destructible_V2* destructible;
};
  • ToilletTissue_V2는 private 상속이 아닌 멤버변수로 destructible을 포함하고 있습니다.
  • ToilletTissue_V2의 Sop 메서드를 보게 되면 descturctible의 Destroy 메서드를 호출하게 되고 이것은 destructible이 실제 어떤 인스턴스인지에 따라 다른 결과를 보이게 될 것입니다.

'디자인 패턴 > 구조' 카테고리의 다른 글

퍼사드(Facade)  (1) 2023.12.22
장식자(Decorator)  (0) 2023.12.22
복합체(Composite)  (2) 2023.12.21
가교(Bridge)  (2) 2023.12.21
구조 패턴  (0) 2023.12.20

+ Recent posts