적응자 (Adapter) 패턴
1. 의도
- 다른 클래스의 인터페이스를 가져와서 새로 만들거나 기존에 있던 클래스에 추가하는 것
2. 활용
- 기존 클래스에서 기능을 추가해야하나 상위 추상 클래스를 손보고 싶지 않을 때
- 다른 클래스에서 기능을 가져오되 다른 클래스 역시 수정하고 싶지 않을 때
- 한마디로 두 개의 인터페이스를 다중 상속하여 두 기능을 갖고 싶을 때 적용
예시 TextShape와 TextView
- 추상 클래스 Shape를 상속받는 Line과 TextShape 객체가 있는데 이들은 특정 모양을 나타내고 그래픽 요소로서 Client가 화면에 그리기 위한 기본 객체이다.
- 객체의 바운더리를 얻는 BoundingBox() 메서드와 이 객체를 다루는 핸들러?를 반환하는 CreateManipulator()가 있다.
- CreateManipulator() 개념은 팩토리 메서드에서 언급하였는데, 각 객체와 클래스 계통이 연결된 특정 핸들러 객체를 생성한다.
- 여기서 TextShape 객체를 그리기에 앞서 추가적으로 객체에 포함된 문자열을 처리하는 복잡한 기능이 필요하다고 하자.
- 우리는 이 기능을 위해 Shape 상위 추상 클래스에 문자열 처리 인터페이스를 추가하기에는 부담스럽다.
- 또한 프레임워크에서 기존 TextView 객체를 Shape에서 상속시켜 새로운 TextShape를 만들기에도 TextView와 관련된 것이 많아 부담스럽다.
- 따라서 Shape의 기본 인터페이스와 TextView의 GetExtent() 인터페이스를 적절히 혼합하여 사용하는 방법이 Adapter 패턴이다.
3. 구조
- Shape와 같이 원래 종속적인 인터페이스를 Target
- 두 인터페이스를 사용하는 Adapter
- Adpater의 인터페이스 적응 대상인 TextView를 Adaptee
4. 종류
적응자 패턴은 두 가지로 구현할 수 있다.
1) 클래스 적응자
- 두 인퍼테이스를 모두 상속받아 Adapter에서 새롭게 구현하는 방법이다.
- Target과 Adaptee가 관련이 적을 때, 또 다중 상속으로 복합 기능을 가진 Adapter로 의미가 클 때 사용한다.
- 객체 적응자보다 Adapter에서 Target과 Adaptee를 합성하는 느낌이 강하다.
- Adaptee를 상속받는 것이기 때문에 Adaptee의 또다른 서브 클래스의 기능을 사용할 수 는 없다.
2) 객체 적응자
- Adaptee 인스턴스를 포함시켜 기존 Adaptee의 구현된 내용을 사용하는 방법이다.
- 예시에서 TextShape의 BoundingBox() 메서드 내부에서 TextView 인스턴스 text의 GetExtent()를 사용한다.
- 클래스 적응자처럼 완전 합체가 불필요하거나 불가능할 때 인스턴스를 통해 기능을 사용한다.
- Adaptee라는 인스턴스는 Adaptee의 서브 클래스를 내포하고 이들의 기능을 Adapter에서 사용가능하게 된다.
3) 추가 고려사항
- Adaptee의 기능을 얼마나 차용할 것인가, Target 인터페이스에 적응시키기위해 들어가는 노력이 적당한가?
- 대체 가능 적응자: 적응시 과도한 인터페이스 구성을 지양하고 최소한의 인터페이스 구성으로 클래스 재사용성을 높인다.
- 양방향 적응자: Adaptee를 통해 Target를 사용하거나 Target를 사용해 Adaptee를 사용함 -> 클래스 적응자로 구현
구현
1. 클래스 적응자를 C++로 구현
class Adapter : public Target, private Adaptee{
// implements Target method
// implements Adaptee method
}
- Target을 public, Adpatee를 private로 상속받는다.
- Target 인터페이스는 Adapter에서 공개되지만 Adaptee 인터페이스는 내부 구현에 필요하기 때문에 보일 필요가 없다.
- Adapter는 Target의 서브 클래스이고 Adaptee의 서브 클래스는 아니게 된다.
2. 대체 가능 적응자 - TreeDisplay 문제
- 고찰
Target에 트리 순회와 관련된 다양한 연산이 있을 때,
Target으로부터 상속받은 인터페이스 중 어떤 인터페이스를 최소한으로 구현하면서 그래픽 요소를 나타낼 수 있을 것 인가?
- 구현해야하는 필요 최소 연산
GetChildren(n): n 노드의 모든 자식을 반환하는 연산
CreateGraphicNode(child): child 노드에 대한 그래픽 노드 객체를 생성하여 반환하는 연산
- 적용 예시
BuildTree(n): 최상위 노드 n으로 부터 재귀적으로 순회하며 그래픽 요소를 나타내는 연산
--- BuildTree(n) 연산
GetChildren(n)
for each child{
AddGraphicNode(CreateGraphicNode(child))
BuildTree(child)
}
2.1 추상 연산
TreeDisplay에 추상 연산 형태로 인터페이스를 제공하고 Adapter가 최소 연산을 구현하는 방식
디렉토리 트리를 순환하며 표시하는 DirectoryTreeDisplay에서 GetChildren(n), CreateGraphicNode(child)을 구현하여 FileSystemEntity를 접근하여 자식노드를 반환할 수 있게 한다.
2.2 위임 객체를 사용
TreeDisplay가 두 연산이 포함된 인터페이스가 포함된 대리 객체, TreeAccessorDelegate에 위임하는 방식
DirectoryBrowser는 TreeAccessorDelegate를 상속받아 Adapter로서 두 연산을 구현한다.
예제 코드
TextShape와 TextView 예시에서 실제 Adapter TextShape를 구현하는 예시 코드
Shape.h
#include "Point.h"
#include "Manipulator.h"
class Shape
{
public:
Shape();
// 왼쪽 아래와 오른쪽 위 좌표를 이용하여 사각형 경계를 구한다.
virtual void BoundingBox(Point& bottomleft, Point& topRight) const;
// Shape를 조작하는 Manipulator를 반환하는 팩토리 메서드
virtual Manipulator* CreateManipulator() const;
};
TextView.h
#include "Point.h"
class TextView
{
public:
TextView();
// 텍스트 상자의 중심점
void GetOrigin(Coord& x, Coord& y) const;
// 텍스트 상자의 가로 세로 길이를 이용하여 사각형 경계를 구한다.
void GetExtent(Coord& width, Coord& height) const;
virtual bool IsEmpty() const;
};
1. 클래스 적응자로 구현
C++에서 public 접근자를 통해 인터페이스 상속을 하여 override를 하여 새롭게 구현이 가능하고 private 접근자를 통해
구현을 상속받아 새로 정의하지 않고 부모의 메서드를 자식 메서드에서 그대로 사용할 수 있다.
TextShape.h
#include "Shape.h"
#include "TextView.h"
class TextShape : public Shape, private TextView
{
public:
TextShape();
virtual void BoundingBox(Point& bottomLeft, Point& topRight) const;
virtual bool IsEmpty() const;
virtual Manipulator* CreateManipulator() const;
};
실제 구현을 보면 BoundingBox()와 IsEmpty() 메서드는 내부에서 TextView 구현을 상속받아 부모 메서드를 그대로 사용하고 있다.
추가적으로 Adapter는 Shape에 적응하기 위해 CreateManipulator를 구현해야한다.
// TextView의 GetOrigin, GetExtent 메서드를 이용하여 BoundingBox 메서드를 구현
void TextShape::BoundingBox(Point& bottomLeft, Point& topRight) const
{
Coord bottom, left, width, height;
GetOrigin(bottom, left);
GetExtent(width, height);
bottomLeft = Point(bottom, left);
topRight = Point(bottom + height, left + width);
}
// TextView 구현을 그대로 이용
bool TextShape::IsEmpty() const
{
return TextView::IsEmpty();
}
// Shape로 상속받은 TextView에는 없는 인터페이스
Manipulator* TextShape::CreateManipulator() const
{
return new TextManipulator(this);
}
2. 객체 적응자로 구현
객체 적응자에서 TextView를 상속받지 않고 내부에서 인스턴스를 사용하기 위해 포인터를 관리한다.
#include "Shape.h"
#include "TextView.h"
class TextShape : public Shape
{
public:
TextShape(TextView* text);
virtual void BoundingBox(Point& bottomLeft, Point& topRight) const;
virtual bool IsEmpty() const;
virtual Manipulator* CreateManipulator() const;
private:
// TextView 인스턴스를 참조하기위한 포인터
TextView* _text;
};
생성자를 통해 TextView 인스턴스를 할당하고 내부 메서드에서 참조를 통해 TextView 메서드를 사용한다.
// 내부 메서드에서 TextView 인스턴스를 이용하여 연산을 호출
TextShape::TextShape(TextView* text)
{
_text = text;
}
void TextShape::BoundingBox(Point& bottomLeft, Point& topRight) const
{
Coord bottom, left, width, height;
_text->GetOrigin(bottom, left);
_text->GetExtent(width, height);
bottomLeft = Point(bottom, left);
topRight = Point(bottom + height, left + width);
}
bool TextShape::IsEmpty() const
{
return _text->IsEmpty();
}
// 클래스 적응자와 동일하게 구현한다.
Manipulator* TextShape::CreateManipulator() const
{
return new TextManipulator(this);
}
참고 자료: GoF 디자인 패턴
'디자인 패턴 > GoF' 카테고리의 다른 글
[디자인 패턴] 구조 패턴 (3) 복합체 Composite (0) | 2021.01.27 |
---|---|
[디자인 패턴] 구조 패턴 (2) 가교 Bridge (0) | 2021.01.27 |
[디자인 패턴] 생성 패턴 (5) 싱글톤 Singleton (2021.02.19 수정) (0) | 2021.01.25 |
[디자인 패턴] 생성 패턴 (4) 원형 Prototype (0) | 2021.01.25 |
[디자인 패턴] 생성 패턴 (3) 팩토리 메서드 Factory Method (0) | 2021.01.24 |