Design Pattern - Adapter

해당 포스팅은 Java 언어로 배우는 디자인 패턴 입문 책을 참고해 작성했습니다.
사용된 코드는 jongmin92/Design-Pattern repository 에서 확인할 수 있습니다.

Adapter 패턴

이미 제공되어 있는 것을 그대로 사용할 수 없을 때, 필요한 형태로 교환하고 사용하는 일이 자주 있습니다. ‘이미 제공되어 있는 것’과 ‘필요한 것’ 사이(서로 다른 두 개)의 ‘차이’를 없애주는 디자인 패턴이 Adapter 패턴 입니다.

Adapter 패턴은 Wrapper 패턴으로 불리기도 하며, 다음과 같은 두 가지 종류가 있습니다.

  • 클래스에 의한 Adapter 패턴 (상속을 사용한 Adapter 패턴)
  • 인스턴스에 의한 Adapter 패턴 (위임을 사용한 Adapter 패턴)

예제 프로그램(1) - 상속을 사용한 Adapter 패턴

만들 예제 프로그램은 주어진 문자열을 아래와 같이 표시하는 간단한 것입니다.

1
2
(Hello)
*Hello*

Banner 클래스에는 문자열을 괄호로 묶어서 표시하는 showWithParen 메소드와 문자열 전후에 *를 붙여 표시하는 showWithAster 메소드가 준비되어 있습니다. (이미 제공되어 있는 것)

Print 인터페이스에는 문자열을 느슨하게(괄호 사용) 표시하기 위한 printWeak 메소드와 문자열을 강하게 표시하기 위한 (* 표시를 앞뒤에 붙여 강조) printStrong 메소드가 선언되어 있습니다.

지금 하고 싶은 일은 Banner 클래스를 사용해서 Print 인터페이스를 충족시키는 클래스를 만드는 일입니다.

PrintBanner 클래스가 어댑터의 역할을 담당합니다. 이 클래스는 제공되어 있는 Banner 클래스를 상속해서, 필요로 하는 Print 인터페이스를 구현합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Banner {
private String string;

public Banner(String string) {
this.string = string;
}

public void showWithParen() {
System.out.println('(' + string + ')');
}

public void showWithAster() {
System.out.println('*' + string + '*');
}
}
1
2
3
4
5
public interface Print {
void printWeak();

void printStrong();
}

PrintBanner 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class PrintBanner extends Banner implements Print {
public PrintBanner(String string) {
super(string);
}

@Override
public void printWeak() {
showWithParen();
}

@Override
public void printStrong() {
showWithAster();
}
}

Main 클래스

1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
Print p = new PrintBanner("Hello");
p.printWeak();
p.printStrong();
}
}

Main 클래스는 Print 인터페이스를 사용하고 있습니다. Banner 클래스나 showWithParen 메소드나 showWithAster 메소드는 Main 클래스 소스 코드 상에서는 완전히 감추어져 있습니다.

예제 프로그램(2) - 위임을 사용한 Adapter 패턴

Main 클래스, Banner 클래스, Print 인터페이스는 예제 프로그램(1)과 동일합니다.

PrintBanner 클래스는 banner 필드에서 Banner 클래스의 인스턴스를 가집니다. 이 인스턴스는 PrintBanner 클래스의 생성자에서 생성합니다.

이전 예와는 달리, 이번에는 필드를 경우해서 호출하고 있습니다. 즉 위임을 하고 있습니다.

PrintBanner 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class PrintBanner implements Print {
private Banner banner;

public PrintBanner(String string) {
this.banner = new Banner(string);
}

@Override
public void printWeak() {
banner.showWithParen();
}

@Override
public void printStrong() {
banner.showWithAster();
}
}

Adapter 패턴의 구성요소

  • Target(대상)의 역할
    지금 필요한 메소드를 결정합니다. 예제 프로그램에서 Print 인터페이스(상속의 경우)나 Print 클래스(위임의 경우)가 이 역할을 합니다.
  • Client(의뢰자)의 역할
    Target 역할의 메소드를 사용해서 일을 합니다. 예제 프로그램에서 Main 클래스가 이 역할을 합니다.
  • Adaptee(개조되는 쪽)의 역할
    Adaptee는 이미 준비되어 있는 메소드를 갖고 있는 역할입니다. Adaptee역의 메소드가 Target 역할의 메소드와 일치하면 다음 Adapter의 역할은 필요없습니다.
  • Adapter의 역할
    Adapter 패턴의 주인공입니다. Adaptee 역할의 메소드를 사용해서 어떻게든 Target 역할을 만족시키기 위한 것이 Adapter 패턴의 목적이며, Adapter 역할의 임무입니다. 예제 프로그램에서는 PrintBanner 클래스가 Adapter의 역할을 합니다.

사고 넓히기

어떤 경우에 사용할까?

Adapter 패턴은 기존의 클래스를 개조해서 필요한 클래스를 만듭니다. 이 패턴으로 필요한 메소드를 빠르게 만들 수 있습니다.

이미 만들어진 클래스를 새로운 인터페이스(API)에 맞게 개조시킬 때 기존 클래스의 소스를 바꾸어서 ‘수정’하려고 합니다. 그러나 그렇게하면 테스트가 이미 끝난 기존의 클래스를 수정한 후에 다시 한 번 테스트 해야 합니다. Adapter 패턴은 기존의 클래스를 전혀 수정하지 않고 목적한 인터페이스(API)에 맞추려는 것입니다.

만약 버그가 발생해도 기존의 클래스(Adaptee의 역할)에는 버그가 없으므로 Adapter 역할의 클래스를 중점적으로 조사하면 되고, 프로그램 검사도 상당히 쉬워집니다.