- 참조: GoF의 디자인 패턴
- 적응자(Adapter) 패턴
적응자 패턴은 다른 이름으로 래퍼(Wrapper)라고 불리우는 패턴이다. 클래스의 인터페이스를 사용자가 원하는 형태로 변환(적응)시킨다. 이렇게 변환(적응)을 통해서 일치하지 않는 인터페이스를 갖는 클래스들이 함께 동작할 수 있도록 한다.
- 시나리오
그림 편집기가 있다고 가정해보자. 그림판의 주요한 추상적 개념은 그래픽 객체들이다. 이런 공통 그래픽 요소에 대한 인터페이스는 추상 클래스인 Shape 에 정의되어 있다. 그리고 각 그래픽 요소인 선과 다각형은 각각 LineShape, PolygonShape 과 같은 클래스로 개발해야 한다.
위와 같이 간단한 도형도 있겠지만 TextShape는 텍스트처리시 버퍼관리와 같이 다른 그래픽 요소에 비해 특별하게 고려해야할 점이 있을 수 있다.
이 때 재사용할 수 있는 라이브러리나 자원이없을까 조사를 하게 된다. 다행히 사용자 인터페이스 툴킷에서 복잡한 TextView를 처리하는 클래스를 제공하고 있다고 가정하자. 당연히 재사용하는것이 바람직하긴 하지만 TextView는 Shape를 고려해서 설계한것이 아니라서 곧바로 TextShape 클래스로 대체하여 사용할 수 없다.
지금이 바로 적응자 패턴을 사용할때이다.
- 적응자 패턴의 구조
적응자 패턴에는 2 가지 구현방식이 있다.
- TextShape(Adapter)가 Shape 의 인터페이스와 TextView 의 구현을 모두 상속
- TextShape(Adapter)가 TextView 의 인스턴스를 포함하고, TextView의 인터페이스를 사용
아래 다이어그램은 위의 2 가지 방법중 인스턴스를 포함한 방식으로 적응자 패턴을 구현한 다이어그램이다.
위에서 TextShape은 TextView 클래스에 정의된 인터페이스를 바꾸어 Shape 클래스에 정의된 인터페이스와 잘 부합되게 한다. 이로써 TextView 클래스를 TextShape 적응자를 통해 재사용할 수 있게 되었다.
또 적응자는 TextView와는 관련이 없지만 Shape 를 구현할 때 만족해야하는, Shape의 객체를 다른 위치로 드래그 할 수 있는 기능도 제공한다. 이로써 Shape 라면 갖추어야 하는 2가지 동작기능을 모두 갖추게 되었다.
적응자 패턴의 참여자는 아래와 같다. (이해하기 쉽게 다이어그램의 클래스 위에 Text를 표기하였다.)
- Client(DrawingEditor): Target 인터페이스를 만족하는 객체와 동작할 대상
- Target(Shape): 사용자(Client)가 사용하는데 필요한 인터페이스를 정의한 클래스
- Adaptee(TextView): 인터페이스에 적응이 필요한 기존 인터페이스, 적응 대상자
- Adapter(TextShape): Target 인터페이스에 Adaptee의 인터페이스를 적응 시키는 클래스
- Java 예제
일단 Shape를 사용하는 DrawingEditor 클래스부터 만들어보자.
public class DrawingEditor {
public void useShape(Shape shape) {
shape.boundingBox();
shape.createManipulator();
}
}
DrawingEditor가 사용하는 Shape 클래스는 아래와 같이 정의하였다.
public interface Shape {
void boundingBox();
void createManipulator();
}
Line은 특별한 처리가 필요하지 않은 평범한 Shape 클래스이다.
public class Line implements Shape {
@Override
public void boundingBox() {
System.out.println("Line bounding box");
}
@Override
public void createManipulator() {
System.out.println("Line create manipulator");
}
}
Adapter인 TextShape이 사용해야할 Adaptee, TextView의 인터페이스는 아래와 같다.
public interface TextView {
void getExtent();
}
이제 Adapter 클래스를 구현해보자.
public class TextShape implements Shape{
private TextView text;
public TextShape(TextView text) {
super();
this.text = text;
}
@Override
public void boundingBox() {
text.getExtent();
System.out.println("TextShape bounding box.");
}
@Override
public void createManipulator() {
System.out.println("TextShape create manipulator.");
}
}
Adaptee를 생성자의 인자를 통해 받아서 속성에 설정하였다. boudingBox의 메소드를 보면 adaptee인 TextView의 메소드를 이용하여 기능을 구현하고 있다. 이 패턴의 참여자들을 이용하여 작성한 Test 코드는 아래와 같다.
public static void main(String[] args) {
DrawingEditor drawingEitor = new DrawingEditor();
Shape lineShape = new Line();
TextView text = () -> {
System.out.println("getExtent called in Text View");
};
Shape textShape = new TextShape(text);
System.out.println("====DrawingEditor use line shape.====");
drawingEitor.useShape(lineShape);
System.out.println();
System.out.println("====DrawingEditor use text shape.====");
drawingEitor.useShape(textShape);
}
lineShape는 특별한것이 없다. 우리가 작성한 코드에서 adaptee인 TextView는 인터페이스만 정의하였고, 구현체는 없었다. TextView는 추상 method가 1개인 FunctionalInterface 이기 때문에 익명함수를 이용하여 구현체 text를 생성하였다. 그리고 adapter인 TextShape에서 TextView 를 생성자 인자로 받으므로, text를 인자로 넘겨주었다.
====DrawingEditor use line shape.====
Line bounding box
Line create manipulator
====DrawingEditor use text shape.====
getExtent called in Text View
TextShape bounding box.
TextShape create manipulator.
- 마치면서
지금까지 적응자 패턴을 알아보았다. adapter 패턴은 실제 프로젝트에서 유용한 패턴이다. 재사용해야 하는 클래스가 있는데 인터페이스가 맞지 않다거나, 재사용 클래스를 사용하고자 하는 어떤 클래스에 맞게 변경할 수 없을 때 사용한다. 위의 코드는 객체 합성을 이용하여 구현했지만, 다중 상속이나 구현을 이용해서도 구현해보길 바란다.
'Concepts > Design Pattern' 카테고리의 다른 글
구조 패턴 - 복합체(Composite) (0) | 2021.05.06 |
---|---|
구조 패턴 - 가교(Bridge) (0) | 2021.05.01 |
객체 생성 패턴- 단일체 (0) | 2021.04.12 |
객체 생성 패턴- 프로토타입 (0) | 2021.02.25 |
객체 생성 패턴- 팩토리 메소드 (0) | 2021.02.21 |
댓글