자바 Iterator 패턴.

이번 글은 자바의 패턴 중 가장 간단하면서 흔히 사용되는 패턴이 바로 Iterator 패턴이라고 한다.

그래서 검색을 해본 결과.
Iterator 는 디자인 패턴에서 가장 간단하고 가장 빈번하게 사용되는 패턴들 중의 하나이다...생략.
이렇게 위와 같이 소개 하고 있다.

디자인 패턴이라.. 프로그래밍 역시 어떠한 구조를 디자인 하는 것이니까 디자인 패턴이라는 말이 적절한 듯 하다.

그럼 2시간의 고뇌로도 풀리지 않았던 Iterator 패턴에 대해서 알아보자.
(시키는 것만 하는 바보처럼 기본만 공부하라는 말을 그대로 따른 자의 최후는 암담했다.
앞으론 그러지 말도록~ 직장에서 그러면 짤린다..)

1. Iterator 인터페이스
 - boolean형을 반환하는 hsaNext() 추상메소드를 갖고 있다.
 - Object형을 반환하는 next() 추상메소드를 갖고 있다.

interface Iterator {

    public abstract boolean hasNext();

    public abstract Object next();

}

### 소스에 대해서는 굳이 언급 안하겠다. 여기서 주의할 점은 추상메소드니까 abstract를 붙여주는 것.

2. Aggregate 인터페이스
 - Iterator형을 반환하는 iterator() 추상메소드를 갖고 있다.

interface Aggregate {

    public abstract Iterator iterator();

}

### Iterator형을 반환한다는데 interface라서 안되는 것이 아닐까 라는 의심을 갖지 마라. 인터페이스는 반드시 구현을 해야 사용이 가능하다. 구현이 가능하다는 것은 다시 말하면 특정 클래스에서 구현을 한다는 것이기에 Iterator이 인터페이스이지만 클래스로 봐도 문제가 없다.

3. Book 클래스
 - 맴버변수 : String형 name을 갖고 있다.
 - 문자열을 인수로 받아 name에 저장하는 생성자를 갖고 잇다.
 - name를 반환하는 getName() 메소드를 갖고 있다.

class Book {

    private String name;


    public Book(String a){

        name = a;

    }


    public String getName(){

        return name;

    }

}

### 여기서는 어려울게 딱히 없다. 복습을 하자면 생성자와 메소드의 차이점, 반환형이 어떤 형태인지, 반환형이 있는지 없는지의 차이점을 다시 떠올려 보는 정도가 좋을 것 같다.

4. Bookshelf 클래스
 - 인터페이스 Aggregate를 구현한다.
 - 맴버변수 : Book 객체를 저장하는 배열 books[]와 int 형 last를 갖고 있다. 단, last는 0으로 초기화 해야 한다.
 - 생성자는 배열의 크기를 지정하는 정수를 인수로 받아 books를 그 크기로 생성한다.
 - books[index]를 반환하는 getBookAt(int index) 메소드를 갖고 있다.
 - Book형 객체를 인수로 받아 books[last]에 저장하고 last를 1 증가 시키는 appendBook(Book book) 메소드를 갖고 있다.
 - 맴버변수 last를 반환하는 getLength() 메소드를 갖고 있다.
 - BookShelfIterator객체를 생성하여 반환하는 메소드 iterator() 메소드를 갖고 있다. 단, this를 인수로 사용해야 한다.

class Bookshelf implements Aggregate{

    private Book books[];

    private int last=0;


    public Bookshelf(int a){

        books = new Book[a];

    }


    public Book getBookAt(int index){

        return books[index];

    }

    public void appendBook(Book book){

        books[last] = book;

        last++;

    }

    public int getLength(){

        return last;

    }


    public Iterator iterator(){

        BookShelfIterator bsIterator = new BookShelfIterator(this);

        return bsIterator;

    }

}

### 위 소스에서 난해하다고 지적할 만한 부분은 마지막 iterator 메소드 일 것이다. 이를 해설하기 전에 배열에 대해서 한번 집고 넘어간 뒤에 해설하자. 여기서 맴버변수 중 books[] 가 존재 할 것이다.
그런데 배열은 흔히 Book books[] = new Book[크기]; 이런식으로 선언한다.
그러나 뒤에 new Book[크기] 이 부분은 제외 시킬수도 있다는 것을 알아두자. Book형 객체를 배열로 만들긴 했으나 얼마 만큼의 크기로 만들지는 정하지 않았다. 불가능한 일일까? 가능하다. 그 이유를 설명하자면
        int i;
        i = 0;
위와 같은 방법으로도 선언해 사용하기 때문이다. 만약 프리미티브, 레퍼런스 그 어떤 형태로 만들어버리든 해당 변수나 객체를 사용을 하지 않는다면 그 내용 또한 없어도 된다. 쉽게 말해서 int i; 이렇게 선언하고 i를 사용하지 않는다면 문제가 될 이유가 없다는 것이다. 이런 이야기를 여기서 길게 하는 이유는 프로그래밍언어는 어떠한 것을 만들어 내는 개발툴이기 때문에 자유도가 상당히 높다는 것을 알려주기 위함이다. 어떤 것을 만들어 내는 기계 또는 도구가 제한 될 사항이 많다면 만들어 낼 수 있는 창조물에는 많은 제한요소가 따라오기 때문에 만들어 낼 수 있는 범위가 적다. 잠깐 이야기가 삼천포로 빠지는 듯하다. 다시 본론으로 돌아와서 이야기 하자.

뭐 길게 이야기 했지만 결과적으로 배열의 크기는 생성자에서 정해주기 때문에 문제가 없다. 여기서 왜? 라는 말을 하지는 말자.
한 클래스 내에서 생성된 변수이므로 생성자 또한 클래스에 속하는 범위라서 문제가 없는 것이니까 문제없다고 믿도록 하자.

그럼 이제 어려울 수 있는 iterator 메소드에 대해서 알아보자. 이 메소드는 인터페이스에 존재 하는 추상메소드를 구현 하는 것이다. 그러니까 기본 틀은 고대로 가져와야 한다. 그렇기 때문에 껍데기는 그대로 가져오고 내용물만 첨가 하면 된다.
말 그대로 BookShelfIterator 객체를 만들어 주자. 지금은 만들지 않았지만(그 것에 내용을 몰라도) 객체를 만들어 반환해야 한다는 조건이 있으므로 그대로 시키는 대로 만들자.

그리고 잊지말자. Iterator은 인터페이스지만 클래스로 부터 구현이 되므로 반환형으로 쓸 수 있다는 것을..

그럼 여기서 한가지 더 this 이녀석 이상하게 골 아프게 만들기도 한다. 그럼 this가 무엇인가에 대해서 다시 생각해보자.
일단 this와 this()는 조금 차이가 있다는 것을 알아두자. 여기서는 this에 대해서만 이야기 할 것이니 나머지는 알아서...-_-;
this는 보통 지역변수와 맴버변수의 이름이 같을 때
class test
{
    private String str = "천재";

    public void getStr(String str){
        System.out.println(str);
        System.out.println(this.str);
    }
}
위처럼 이용하는 것이다. 이는 가장 일반적이고 누구나 쉽게 알 수 있는 this의 특징이다. 그러나 this를 조금 더 유의있게 봐둬야 할 필요가 있다. this.맴버변수 이런식으로 쓴다 하지만 그냥 this를 쓴다면 어떻게 될까?
class test
{
    private String str = "천재";

    public void printMethod(){
        System.out.println(this);
    }
}
신기하게도 자신을 출력한다. 메인 메소드에서 위에 printMethod를 실행하게 하면 test@1234 이런식으로 화면에 보여주게 된다. 이것은 자기 자신을 출력한다는 말인데 test로 객체를 생성하게 되면 이는 레퍼런스형 변수가 된다. 레퍼런스형 변수의 특징은 자신이 값을 가지고 있는 것이 아니라 값이 존재하는 곳의 주소를 자신이 가지고 있다는 것이다. 프로그램이 구동 되는 동시에 메모리에는 메인이 올라가고 그리고 test클래스도 뒤 따라 올라가게 된다. 그럼 메인에 있는 test형 객체는 test클래스가 존재하는 곳에 주소를 갖게 된다.(test클래스의 값들이 존재하는 영역)
조금 설명이 모자른 듯 하지만 계속 이야기 하자면 메인에서는 printMethod()를 실행하게 되면 이제 화면에 무엇인가를 보여줘야 하는 System.out.println(this)가 실행 되서 화면에 보여줘야 하는데 여기서 this가 출력된다. 이것은 객체자체가 갖고 있는 것을 달라는 것과 같은데(잘 이해하자. 객체가 지시하는 주소에 값을 출력하는 것이 아니라 자신이 갖고 있는 주소 자체를 말하는것이다.) 이는 this와 메인의 객체가 같다 라는 식으로 해석해도 될 것이다. 그 말은 this가 자신을 나타낸다고 할 수 있을 것 같다.

그럼 일단 설명은 여기까지 하겠다..완벽하게 이해 한것 같지는 않지만 머릿속에는 분명히 어느정도 이해를 하고 있기 때문이다..
(답답하다 좀 설명이 이상한듯 하기도 해서...)

아무튼...그런 이유로
BookShelfIterator bsIterator = new BookShelfIterator(this);
위에 라인에서 this는 Bookshelf를 나타내는 것이라 할수 있을 것이다.
그럼 다음으로..;

5. BookShelfIterator 클래스
 - Iterator 인터페이스를 구현한다.
 - 맴버변수 : Bookshelf형 객체 bookshelf와 int index를 갖고 있다.
 - 생성자는 Bookshelf형 객체를 인수로 받아 bookshelf에 대입하고 index를 0으로 초기화 한다.
 - bookshelf에서 index의 위치에 가져올 객체가 있으면 true, 없으면 false를 반환하는 hasNext() 메소드를 갖고 있다.
 - bookshelf에서 index의 위치의 객체를 가져온 뒤 index를 1 증가시키고 가져온 객체를 반환하는 next() 메소드를 갖고 있다.

class BookShelfIterator implements Iterator{

    private Bookshelf bookshelf;

    private int index;


    public BookShelfIterator(Bookshelf a){

        bookshelf = a;

        index = 0;

    }

    public boolean hasNext(){

        if((bookshelf.getLength()) > index){

            return true;

        }

        else{

            return false;

        }

    }

    public Object next(){

        int i = index;

        index++;

        return bookshelf.getBookAt(i);

    }

}

### 여기서 중요하게 다뤄야 할것이 있다면 Bookshelf 클래스에서 iterator() 메소드와 BookShelfIterator 클래스의 생성자와의 비교이다. 여기서 서로 연결고리가 성립되는데 BookShelfIterator의 생성자는 인수로 Bookshelf형 객체를 받아서 자신의 맴버변수에게 값을 넣는 일을 한다. iterator()메소드에서는 BookShelfIterator 객체를 생성하는데 여기서 this가 쓰인다는 점이다. BookShelfIterator 생성자는 분명 Bookshelf의 객체를 인수로 받는다고 했다. 그 말은 this가 그 역할을 한다는 것이 증명된다.
그럼 이제 확실히 알고 가자. this가 무엇을 하는 것인지에 대해서 말이다. 알겠는가? this는 자기 자신을 가리킨다는 것을..
그럼 여기에서는 더 이상 볼일이 없는 것 같다. 설마 메소드의 반환을 하는 return에 대해서 갸우뚱 하지는 말자..
여러 조건 중 하나의 조건을 리턴 하는 것은 그다지 놀라운 일이 아니니까..
(아까도 말했듯이 개발환경의 자유도라는거다 자유도..)

6. Main 클래스
 - 길이 3 인 Bookshelf 객체를 만든다.
 - Book 객체 3개를 생성하고 Bookshelf 에 Book의 내용을 저장한다.
 - Bookshelf가 반환해 주는 Iterator(BookShelfIterator에서 구현했다)객체를 이용하여 저장된 Book 객체를 하나씩 가져와 그 내용(name)을 test.txt 라는 파일에 한 줄에 하나씩 출력하라.

import java.io.*;

public class Main {

    public static void main(String[] args) {

        Bookshelf bs = new Bookshelf(3);


        Book b1 = new Book("자바책");

        Book b2 = new Book("수학책");

        Book b3 = new Book("국어책");


        bs.appendBook(b1);

        bs.appendBook(b2);

        bs.appendBook(b3);


        Iterator it = bs.iterator();


        try{

            File fw = new File("test.txt");

            PrintWriter pw = new PrintWriter(new FileWriter(fw));


            while(it.hasNext()){

                Book bw = (Book)it.next();

                pw.println(bw.getName());

            }

            pw.flush();

            pw.close();

        }catch(IOException e){}

    }

}

### 메인에서 구현하는 과정은 5번까지 모두 구현을 했다면 시키는 대로만 하면 된다. 조금 어렵게 느껴질 수 있는 부분이 있는데 벡터를 생각해보자. 메인에서 만들라 하는 조건은 대체적으로 벡터를 이용하는 것과 비슷한 내용이다.

메인은 만든것을 써먹은 클래스이므로 이쯤에서 끝내도록 하자.


여기까지 어렵게 해설을 하면서 겨우 이번 글을 완성 했다. 이번 글을 쓰면서 본인도 많은 것을 이해하게 됐다.
대체적으로 패턴이라는 것을 알고 있으면 여러가지를 만드는데 있어서 훨씬 더 수월하고 코드도 규칙있고 해석이 쉽게 짜여지는 것을 알 수 있었다. 확실히 머릿속에 어떻게 해야겠다 라는 그림이 빨리 그려지기도 하는 것을 보면 그렇다..

조금 늦은 감이 있지만 자바에 대한 기초는 여기까지 할 수 있다면 일단은 자바를 배울 준비는 잘 된듯 하다. 이후에 배워야 하는 것은 네트워크 프로그래밍, DB 등 아직 배우지 못한 부분이고 라이브러리를 얼마나 많이 알고 있느냐에 달린 문제이기도 하기 때문에 객체지향 프로그래밍을 할 수 있는 조건을 갖추는 것은 iterator 패턴까지 확실히 이해하는 정도면 적당한 것 같다.

패턴 자체가 배웠던 것을 적절히 다 이용하기 때문이다.

이 글과 관련있는 글을 자동검색한 결과입니다 [?]

by 만성피로 | 2008/09/19 21:17 | 정리할 자료들 | 트랙백 | 덧글(2)

트랙백 주소 : http://maydaisy.egloos.com/tb/848313
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Commented by 김교수 at 2008/09/23 23:05
참으로 훌륭한 해설입니다. 모든 학생들에게 읽어보라고 권하고 싶군요.
Commented by Nc_Cracker at 2008/09/23 23:09
대박이네 ㅊㅋ ㅋㅋㅋ

:         :

:

비공개 덧글

◀ 이전 페이지다음 페이지 ▶