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 역할의 클래스를 중점적으로 조사하면 되고, 프로그램 검사도 상당히 쉬워집니다.

Design Pattern - Iterator

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

Iterator 패턴

Java 언어에서 배열 arr의 모든 요소를 표시하기 위해서는 다음과 같이 for문을 사용합니다.

1
2
3
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}

for문의 i++에서 i를 하나씩 증가시키면서 배열 arr의 요소 전체를 처음부터 차례대로 검색하게 됩니다. 여기서 사용되고 있는 변수 i의 기능을 추상화해서 일반화한 것을 디자인 패턴에서는 Iterator 패턴이라고 합니다.

Iterator 패턴이란, 무엇인가 많이 모여있는 것들을 순서대로 지정하면서 전체를 검색하는 처리를 실행하기 위한 것입니다. Iterator는 무엇인가를 ‘반복한다’라는 의미이며, 반복자라고도 합니다.

예제 프로그램

Iterator 패턴을 사용해 책장에 꽂혀 있는 책들을 하나씩 검색해 책 이름을 출력해보는 예제 프로그램을 작성해 보겠습니다.

Aggregate 인터페이스

Aggregate 인터페이스는 요소들이 나열되어 있는 ‘집합체’를 나타냅니다. 이 인터페이스를 구현하고 있는 클래스는 배열과 같이 무엇인가가 많이 모여 있습니다.

1
2
3
public interface Aggregate {
Iterator iterator();
}

Aggregate 인터페이스에 선언되어 있는 메소드는 iterator 메소드 하나뿐입니다. 이 메소드는 집합체에 대응하는 Iterator를 1개 작성하기 위한 것입니다.

Iterator 인터페이스

Iterator 인터페이스는 요소를 하나씩 나열하면서 루프 변수와 같은 역할을 수행합니다.

1
2
3
4
public interface Iterator() {
boolean hasNext();
Object next();
}

hasNext 메소드는 다음 요소가 존재하는지를 조사하기 위한 메소드입니다. 다음 요소가 존재하면 true를 반환하고, 다음 요소가 존재하지 않는 마지막 요소라면 false를 반환합니다. 즉, hasNext는 루프의 종료 조건으로 사용됩니다.

next 메소드는 집합체의 요소를 1개 반환합니다. 또한 next 메소드를 호출했을 때 다음 요소를 반환하도록 내부 상태를 다음으로 진행시켜 두는 역할도 함께합니다.

Book 클래스

Book 클래스는 책을 나타내는 클래스입니다.

1
2
3
4
5
6
7
8
9
10
11
public class Book {
private String name;

public Book(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

BookShelf 클래스

BookShelf 클래스는 책장을 나타내는 클래스입니다. 이 클래스를 집합체로 다루기 위해 Aggregate 인터페이스를 구현합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class BookShelf implements Aggregate {
private Book[] books;
private int last = 0;

public BookShelf(int maxsize) {
this.books = new Book[maxsize];
}

public Book getBookAt(int index) {
return books[index];
}

public void appendBook(Book book) {
this.books[last] = book;
last++;
}

public int getLength() {
return last;
}

@Override
public Iterator iterator() {
return new BookShelfIterator(this);
}
}

iterator 메소드는 BookShelf 클래스에 대응하는 Iterator로서, BookShelfIterator라는 클래스의 인스턴스를 생성해서 그것을 반환합니다.

BookShelfIterator 클래스

BookshelfIterator 를 Iterator로서 다루기 위해 Iterator 인터페이스를 구현합니다. bookShelf 필드는 BookShelfIterator가 검색할 책장이고, index 필드는 현재 가리키는 책을 가리키는 첨자입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 public class BookShelfIterator implements Iterator {
private BookShelf bookShelf;
private int index;

public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
this.index = 0;
}

@Override
public boolean hasNext() {
return index < bookShelf.getLength();
}

@Override
public Object next() {
Book book = bookShelf.getBookAt(index);
index++;
return book;
}
}

Main 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf(4);
bookShelf.appendBook(new Book("Effective Java"));
bookShelf.appendBook(new Book("Head First Java"));
bookShelf.appendBook(new Book("Thinking In Java"));
bookShelf.appendBook(new Book("Agile Java"));

Iterator it = bookShelf.iterator();
while (it.hasNext()) {
Book book = (Book) it.next();
System.out.println(book.getName());
}
}
}
1
2
3
4
5
// 실행결과
Effective Java
Head First Java
Thinking In Java
Agile Java

Iterator 패턴의 구성요소

  • Iterator(반복자)의 역할
    요소를 순서대로 검색해가는 인터페이스(API)를 결정 (hasNext, next)

  • ConcreteIterator(구체적인 반복자)의 역할
    Iterator가 결정한 인터페이스(API)를 실제로 구현

  • Aggregate(집합체)의 역할
    Iterator 역할을 만들어내는 인터페이스(API)를 결정

  • ConcreteAggregate(구체적인 집합체)의 역할
    Aggregate 역할이 결정한 인터페이스(API)를 실제로 구현

사고 넓히기

구현에 상관 없이 Iterator를 사용할 수 있다.

1
2
3
4
while (it.hasNext()) {
Book book = (Book) it.next();
System.out.println(book.getName());
}

여기서 사용되고 있는 것은 hasNext와 next라는 Iterator의 메소드 뿐입니다. BooShelf의 구현에서 사용되고 있는 메소드는 호출되고 있지 않습니다. 결국 위 코드의 while 루프는 BookShelf의 구현에 의존하지 않습니다.

그렇기 때문에 현재 배열을 사용해 구현하고 있는 BookShelf를 List를 사용하도록 수정해도, 위의 while 루프는 전혀 변경하지 않아도 동작합니다.

Aggregate와 Iterator의 대응

BookShelfIterator는 BookShelf가 어떻게 구현되고 있는지 알기 때문에, ‘다음 책’을 얻기 위해 getBookAt 메소드를 호출할 수 있었습니다.

만약 BookShelf의 구현을 전부 변경하고, getBookAt 메소드라는 인터페이스(API)도 변경된다면 BookShelfIterator의 수정이 필요하게 됩니다.

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×