'디자인패턴 for JAVA'에 해당되는 글 2건

  1. 2009/08/18 Adapter 패턴
  2. 2009/05/06 Iterator 패턴

프로그램의 세계에서도 이미 제공되어 있는 것을 그대로 사용할 수 없을 때, 필요한 형태로 교환하고 사용하는 일이 자주 있습니다. '이미 제공되어 있는 것'과 '필요한 것' 사이에 '차이를 없애주는 디자인 패턴이 Adapter 패턴 입니다.

 

Adapter 패턴은 Wrapper 패턴으로 불리기도 합니다. wrapper는 '감싸는 것'이라는 의미를 가지며, 무엇인가를 한번 포장해서 다른 용도로 사용할 수 있게 교환해 주는 것이 wrapper 이며 adapter입니다.

 

Adapter 패턴에는 두 가지 종류가 있다.

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

 

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

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

 

한편 Print 인터페이스에서는 문자열을 느슨하게(괄호 사용) 표시하기 위한 printWeak 메소드와 문자열을 강하게 (* 표시를 앞뒤에 붙여 강조) 표시하기 위한 printString 메소드가 선언되어 있다고 가정합니다.  지금 하고 싶은 일은 Banner 클래스를 사용해서 Print 인터페이스를 충족시키는 클래스를 만드는 일입니다.

 

PrintBanner 클래스가 어댑터의 역할을 담당합니다. 이 클래스는 제공되어 있는 Banner 클래스를 상속해서, 필요로 하는 Print 인터페이스를 구현합니다. PrintBanner 클래스는 showWithParen 메소드를 사용해서 printWeak를 구현하고, showWithAster 메소드를 이용해서 printString 메소드를 구현합니다. 이것으로 PrintBanner 클래스는 어댑터의 기능을 완수하게 됩니다.

 

예제 1.  Banner 클래스(Banner.java)

  1. package Adapter;

    public class Banner {
        // 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 + "*");
        }
    }

 

예제 2. Print 인터페이스(Print.java)

  1. package Adapter;

    public interface Print {
        public abstract void printWeak();
        public abstract void printStrong();
    }

 

예제 3. PrintBaner 클래스(PrintBanner.java)

  1. package Adapter;

    public class PrintBanner extends Banner implements Print {
       
        public PrintBanner(String string){
            super(string);
        }
       
        public void printWeak(){ // 인터페이스 Print.printWeak() 구현
            showWithParen();
        }
       
        public void printStrong(){ // 인터페이스 Print.printString() 구현
            showWithAster();
        }

    }

 

예제 4. PrintBannerMain 클래스(PrintBannerMain.java)

  1. package Adapter;

    public class PrintBannerMain {
        public static void main(String[] args){
            Print p = new PrintBanner("Hello");
            p.printWeak();
            p.printStrong();
        }
    }

 

실행결과

PrintBannerMain.JPG

 

PrintBannerMain 클래스 내에서 PrintBanner의 인스턴스를 Print 인터페이스형의 변수로 대입하는 것에 주의해야 한다. 이 PrintBannerMain는 어디까지나 Print라는 인터페이스를 사용해서 프로그래밍을 하고 있다. Banner 클래스나 showWithParen 메소드나 showWithAster 메소드는 Main 클래스의 소스 코드상에서는 완전히 감추어져 있다.

 

PrintBannerMain는 PrintBanner 클래스가 어떻게 실현되고 있는지를 모른다. 실행되는 것을 모른다는 것은 PrintBannerMain 클래스를 전혀 변경하지 않고 PrintBanner 클래스의 구현을 바꿀 수 있다는 것이다.

 

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

이전 예제 프로그램(1)은 '클래스에 의한' Adapter 패턴이었습니다. 이번에는 '인스턴스에 의한' Adapter 패턴을 살표봅시다. 예제 프로그램 (1)에서는 '상속'을 사용했지만, 이번에는 '위임'을 사용한다. (위임과 관련된 내용 링크)

 

PrintBannerMain 클래스, Banner 클래스는 예제 프로그램(1)과 동일합니다. 그러나 Print2는 인터페이스가 아니고 클래스라고 가정합니다. 즉, Banner 클래스를 이용해서 Print2 클래스와 동일한 메소드를 갖는 클래스를 실현하려는 것입니다. Java 에서는 2 개의 클래스를 동시에 상속할 수 없기 때문에(단일 상속), PrintBanner2 클래스를 Print와 Banner 모두의 하위 클래스로 정의할 수 없습니다.

 

PrintBanner2 클래스는 banner 필드에서 Banner 클래스의 인스턴스를 가집니다. 이 인스턴스는 PrintBanner2 클래스의 생성자에서 생성합니다. 그리고 printWeak 및 printStrong 메소드에서는 banner 필드를 매개로 showWithParen, showWithAster 메소드를 호출합니다.

 

이전 예에서는 자신의 상위 클래스에서 상속한 showWithParen, showWithAster 메소드를 호출하고 있지만, 이번에는 필드를 경유해서 호출하고 있습니다.

 

PrintBanner2 클래스의 printWeak 메소드가 호출되었을 떄, 자신이 처리하는 것이 아니라 별도의 인스턴스인 showWithParen 메소드에게 위임하고 있습니다.

 

예제 4. Print2 클래스(Print2.java)

  1. package Adapter;

    public abstract class Print2 {
        public abstract void printWeak();
        public abstract void printStrong();
    }

 

예제 5. PrintBanner2 클래스(PrintBanner2.java)

  1. package Adapter;

    public class PrintBanner2 extends Print2 {
        private Banner banner; // Banner 를 위임하여 받음
       
        public PrintBanner2(String string){
            this.banner = new Banner(string);
        }

        public void printWeak(){
            banner.showWithParen();
        }
       
        public void printStrong(){
            banner.showWithAster();
        }
    }

 

예제 6. PrintBannerMain2 클래스(PrintBannerMain2.java)

  1. package Adapter;

    public class PrintBannerMain2 {
        public static void main(String[] args){
            Print2 p = new PrintBanner2("Hello");
            p.printWeak();
            p.printStrong();
        }
    }

 

Adapter 패턴의 등장인물

  • Target(대상)의 역할
    지금 필요한 메소드를 결정합니다. 노트북을 작동시키기 위한 직류 12볼트에 해당합니다. 예제 프로그램에서는 Print 인터페이스(상속)나 Print 클래스(위임)가 이 역할을 합니다.

 

  • Client(의뢰자)의 역할
    Target 역할의 메소드를 사용해서 일을 합니다. 직류 12볼트로 움직이는 노트북에 해당합니다. 예제 프로그램에서는 PrintBannerMain 클래스가 이 역할을 수행합니다.

 

  • Adaptee(개조되는 쪽)의 역할
    Adapt-er(개조하는 쪽)가 아니고 Adapt-ee(개조되는 쪽)입니다. Adaptee는 이미 준비되어 있는 메소드를 가지고 있는 역할입니다. 교류 100볼트의 AC 전원에 해당되며, 예제 프로그램에서는 Banner 클래스가 이 역할을 합니다. 이 Adaptee역의 메소드가 Target 역할의 메소드와 일치하면(노트북 사용 전압과 가정에 공급되는 전압이 같다면 어답터가 필요 없음) 다음 Adapter의 역할은 필요없습니다.

 

  • Adapter 의 역할
    Adapter 패턴의 주인공이니다. Adaptee 역할의 메소드를 사용해서 어떻게든 Target 역할을 만족시키기 위한 것이 Adapter 패턴의 목적이며, Adapter 역할의 임무입니다. 예제 프로그램에서는 PrintBanner 클래스가 Adapter의 역할을 합니다.

    클래스에 의한 Adapter 패턴의 경우에는 Adapter의 역할은 '상속'을 사용한 Adaptee의 역할을 이용하지만, 인스턴스에 의한 Adapter 패턴의 경우에는 '위임'을 사용한 Adaptee의 역할을 이용합니다.

 

버전 업과 호환성

소프트웨어는 버전 업이 필요합니다. 소프트웨어를 버전 업할 때는 '구 버전과 호환성'이 문제가 됩니다. 구 버전을 버리면 소프트웨어의 유지와 보수는 편하지만 항상 그것이 가능할 순 없습니다. 구 버전을 버리면 소프트웨어의 유지와 보수는 편하지만 항상 그것이 가능할 순 없습니다. 구 버전과 신 버전을 공존시키고, 유지와 보수도 편하게 하기 위해서 Adapter 패턴이 도움이 되는 경우가 있습니다. 예를 들어 앞으로는 신 버전만 유지와 보수를 하고 싶을 때 신 버전을 Adaptee 역할로 하고, 구 버전을 Target 역할로 합니다. 그리고 신 버전의 클래스를 사용해서 구 버전의 메소드를 구현하는 Adpater 역할의 클래스를 만듭니다. 

 

관련 패턴

  • Bridge 패턴
    Adapter 패턴은 인터페이스(API)가 서로 다른 클래스를 연결하는 패턴입니다. Bridge 패턴은 기능의 계층과 구현의 계층을 연결시키는 패턴입니다.
  • Decorator 패턴
    Adapter 패턴은 인터페이스(API)의 차이를 조정하기 위한 패턴입니다. Decorator 패턴은 인터페이스(API)를 수정하지 않고 기능을 추가하는 패턴입니다.

이 글은 스프링노트에서 작성되었습니다.

'디자인패턴 for JAVA' 카테고리의 다른 글

Adapter 패턴  (0) 2009/08/18
Iterator 패턴  (0) 2009/05/06
Posted by 허니몬

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

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

  3. }

위에서 사용되고 있는 루프 변수 i를 살펴보겠습니다. 변수 i는 처음에 0으로 초기화되어 1, 2, 3, ... 으로 증가합니다. 그 때마다 arr[i]의 내용이 표시됩니다. 이와 같은 for문은 자주 사용됩니다. 배열은 요소가 많이 모여있는 것으로, 첨자를 지정하면 많은 교수 중 1개를 선택할 수 있습니다.

arr[0]   최초의 요소( 0번째 요소)

arr[1]   그 다음의 요소( 1번째 요소)

...

arr[i]   i번째 요소

...

arr[arr.length - 1 ]   최후의 요소

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

Interator 패턴

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

iterator 는 무엇인가를 '반복한다'라는 의미이며, 반복자(反復子)라고도 합니다. 이번 장에서는 Iterator 패턴을 배우겠습니다.


예제 프로그램

Iterator 패턴을 사용하여 만든 예제 프로그램을 살펴봅시다. 여기에서 작성할 예제 프로그램은 서가(BookShelf) 안에 책(Book)을 넣고, 그 책의 이름을 차례대로 표시하는 프로그램 입니다(그림 1 - 1).

Fig1-1.jpg

Aggregate 인터페이스

Aggregate 인터페이스(리스트 1 - 1 ) 는 요소들이 나열되어 있는 '집합체'를 나타냅니다. 이 인터페이스를 구현하고 있는 클래스는 배열과 같이 무엇인가가 많이 모여있습니다. aggregate는 '모으다', '모이다', '집합'이라는 의미입니다.

Fig1-2.jpgTable_1-1.jpg

리스트 1-1 Aggregate 인터페이스(Aggregate.java)

  1. public interface Aggregate {
        public abstract Iterator iterator();
    }

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

집합체를 하나씩 나열하고, 검색하고 싶을 때에는 iterator 메소드를 사용해서 Iterator 인터페이스를 구현한 클래스의 인터페이스를 만듭니다.


Iterator 인터페이스

다음으로 Iterator 인터페이스( 리스트 1 - 2 )를 살펴봅시다. Iterator 인터페이스는 요소를 하나씩 나열하면서 루프 변수와 같은 역할을 수행합니다. Iterator에는 어떤 메소드가 필요할까요? 다양한 기법이 있지만, 여기서는 가장 단순한 형태의 Iterator 인터페이스를 만들어 보았습니다.


리스트 1-2 Iterator 인터페이스(Iterator.java)

  1. public interface Iterator {
        public abstract boolean hasNext();
        public abstract Object next();
    }


여기에 선언되어 있는 메소드는 2개 입니다. '다음 요소'가 존재하는지를 조사하기 위한 hasNext 메소드와 '다음 요소'를 얻기 위한 next 메소드 입니다. hasNext 메소드의 반환 값이 boolean 형인 이유가 이해가 될 것입니다(다음 요소가 있으면 True, 다음 요소가 없으면 False). 다음 요소가 존재하면 이 메소드는 True를 반환하고, 다음 요소가 존재하지 않는 마지막 요소라면 이 메소드의 반환값은 False 가 됩니다. 즉, hasNext 메소드를 루프의 종료 조건으로 사용하기 위한 것입니다.

next 메소드는 약간의 설명이 필요합니다. 반환값의 행태가 Object로 되어있는 것에서 알 수 있듯이 next 메소드는 집합체의 요소를 1개 반환해 줍니다. 그러나 next 메소드의 역할은 그것만이 아닙니다. 다음 next 메소드를 호출했을 때 정확히 다음 요소를 반환하도록 내부 상태를 다음으로 진행시켜 두는 역할이 뒤에 숨어있습니다. 그러나 '뒤에 숨어 있다'고 하더라도 Iterator 인터페이스에서는 메소드 이름만 알 수 있습니다. 구체적인 역할은 Iterator 인터페이스를 구현하고 있는 클래스(BookShelfIterator)에서 살펴봅시다. 그러면 next 메소드의 역할이 좀 더 확실해질 것입니다.


Book 클래스

Book 클래스( 리스트 1 - 3 )는 책을 나타내는 클래스입니다. 할 수 있는 일은 책 이름을 getName() 메소드에서 얻는 일 뿐입니다. 책 이름은 생성자(Constructor)에서 인스턴스 초기화할 때 인수로 지정합니다.


리스트 1 - 3 Book 클래스(Book.java)

  1. public class Book {
        private String name;

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

        public String getName() {
            return name;
        }
    }


BookShelf 클래스

BookShelf 클래스( 리스트 1 - 4 )는 서가를 나타내는 클래스 입니다. 이 클래스를 집합체로 다루기 위해 Aggregate 인터페이스를 구현하고 있습니다. 소스 안의 'implements Aggregate'가 Aggregate 인터페이스를 구현하고 있다는 것을 나타냅니다. 또한 Aggregate 인터페이스에서 선언되어 있던 iterator 메소드가 기술되어 있는 것도 확인할 수 있습니다.

리스트 1 - 4 BookShelf 클래스(BookShelf.java)

  1. 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;
        }
    public Iterator iterator() {
            return new BookShelfIterator(this);
        }

    }

이 서가는 books 라는 필드를 가지고 있습니다. 이 필드는 Book의 배열입니다. 이 배열의 크기(maxsize)는 처음에 BookShelf의 인스턴스를 만들 때 지정합니다. books 필드를 private 로 한 이유는, 이 클래스 외부로부터 뜻하지 않게 변경되는 것을 방지하기 위해서 입니다.

iterator 메소드는 BookShelf 클래스에 대응하는 Iterator로서, BookShelfIterator 라는 클래스의 인스턴스를 생성해서 그것을 반환합니다. 이 서가의 책을 하나씩 나열하고 싶을 때는 iterator 메소드를 호출합니다.


BookShelfIterator 클래스

BookShelf 클래스의 검색을 실행하는 BookShelfIterator 클래스( 리스트 1 - 5 )를 살펴봅시다.


리스트 1 - 5  BookShelfIterator 클래스(BookShelfIterator.java)

  1. public class BookShelfIterator implements Iterator {
       
        private BookShelf bookShelf;
        private int index;

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

        public boolean
    hasNext() {
            if ( index < bookShelf.getLength() ) {
                return true;
            } else {
                return false;
            }
        }

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

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

생성자에서는 전달된 BookShelf의 인스턴스를 bookShelf 필드에 저장하고 index를 0으로 합니다. hasNext 메소드는 Iterator 인터페이스에서 선언되어 있는 메소드를 구현한 것입니다. '다음 책'이 있는지를 조사해서 있으면 true, 없으면 false를 반환합니다. 다음 책이 있는지 없는지는 index가 서가에 있는 책의 권수(식 bookShelf.getLength()의 값)보다 작은지, 큰지로 판정합니다.

next 메소드는 현재 처리하고 있는 책(Book의 인스턴스)을 반환하고, 다시 '다음'으로 진행시키기 위한 메소드 입니다. Iterator 인터페이스에서 선언되어 있는 메소드로써 조금 복잡합니다. 우선 반환값으로 반환해야 할 책을 book이라는 변수로 저장해 두고 index를 다음으로 진행시킵니다.

'index를 다음으로 진행'시키는 처리는 서두에서 언급한 for문의 i++에 해당하는 처리입니다. 루프변수를 '다음'으로 진행시킨 것입니다.


Main 클래스

이것으로 서가를 검색할 준비가 끝났습니다. 그러면 Main 클래스( 리스트 1 - 6 )를 이용해서 작은 서가를 만들어 봅시다.


리스트 1 - 6 Main 클래스(Main.java)

  1. public class Main {
        public static void main(String[] args)    {
            BookShelf bookShelf = new BookShelf(4); // 서고에 있는 책 수 정의
            bookShelf.appendBook( new Book("Around the world in 80 days") );
            bookShelf.appendBook( new Book("Bible") );
            bookShelf.appendBook( new Book("Cinderella") );
            bookShelf.appendBook( new Book("Daddy-Long-legs") );
            Iterator it = bookShelf.iterator();
            while ( it.hasNext() ) {
                Book book = (Book)it.next();
                System.out.println(book.getName());
            }
        }
    }

우선 책이 4권 들어가는 서가를 만듭니다. 그리고 순서대로

  • Around the world in 80 days

  • Bible

  • Cinderalla

  • Daddy-Long-Legs

라는 4권의 책을 만들었습니다. 책 이름은 순서를 알기 쉽도록 첫 글자의 A, B,C, ... 알파벳 순서로 하였습니다.

bookShelf.iterator()에 의해 얻어지는 it가 서가를 검색하기 위한 Iterator의 인스턴스입니다. while의 조건 부분에 쓰는 것은 물론 it.hasNext() 입니다. 이것으로 책이 있는 한 while 루프가 돌아가고, 루프 내에서 it.next()에 의해 책을 한 권씩 조사하게 됩니다. 실행 결과는 그림 1-3과 같습니다.

그림 1 - 3

Around the world in 80 days
Bible
Cinderella
Daddy-Long-legs


Iterator 패턴의 등장인물

예제 프로그램을 살펴보았으니, 이제 Iterator 패턴의 등장 인물을 정리해 봅시다.


Iterator(반복자)의 역할

요소를 순서대로 검색해가는 인터페이스(API)를 결정합니다. 예제 프로그램에서 Iterator 인터페이스가 그 역할을 하고, 다음 요소가 전재하는지를 얻기 위한 hasNext() 메소드와 다음 요소를 얻기 위한 next 메소드를 결정합니다.


ConcreteIterator(구체적인 반복자)의 역할

Iterator가 결정한 인터페이스(API)를 실제로 구현합니다. 예제 프로그램에서는 BookShelfIterator 클래스가 이 역할을 하였습니다. 이 역할은 검색하기 위해 필요한 정보를 가지고 있어야 합니다. 예제 프로그램에서는 BookShelf 클래스의 인스턴스는 bookShelf 필드에서, 처리되고 있는 책은 index 필드에 기억되어 있었습니다.


Aggregate(집합체)의 역할

iterator 역할을 만들어내는 인터페이스(API)를 결정합니다. 이 인터페이스(API)는 '내가 가지고 있는 요소를 순서대로 검색해 주는 사람'을 만들어 내는 메소드 입니다. 예제 프로그램에서는 Aggregate 인터페이스가 이 역할을 담당하며 iterator 메소드를 결정합니다.


ConcreAggregate(구체적인 집합체)의 역할

Aggregate 역할이 결정한 인터페이스(API)를 실제로 구현하는 일을 합니다. 구체적인 Iterator 역할, 즉 ConcreIterator 역할의 인스턴스를 만들어냅니다. 예제 프로그램에는 BookShelf 클래스가 이 역할을 담당하며, iterator 메소드를 구현합니다. 이상의 Iterator 패턴을 클래스 다이어그램으로 표기하면 그림 1 - 4와 같습니다.

Fig1-4.jpg


이 글은 스프링노트에서 작성되었습니다.

'디자인패턴 for JAVA' 카테고리의 다른 글

Adapter 패턴  (0) 2009/08/18
Iterator 패턴  (0) 2009/05/06
Posted by 허니몬
이전버튼 1 이전버튼