본문 바로가기
Concepts/Design Pattern

구조 패턴 - 적응자(Adapter)

by ocwokocw 2021. 4. 23.

- 참조: 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 패턴은 실제 프로젝트에서 유용한 패턴이다. 재사용해야 하는 클래스가 있는데 인터페이스가 맞지 않다거나, 재사용 클래스를 사용하고자 하는 어떤 클래스에 맞게 변경할 수 없을 때 사용한다. 위의 코드는 객체 합성을 이용하여 구현했지만, 다중 상속이나 구현을 이용해서도 구현해보길 바란다.

댓글