행동 관련 : 이터레이터 패턴 (Iterator Pattern)

    들어가기 전

    이 글은 인프런 백기선님의 GOF 강의를 복습하며 작성한 글입니다.

     


    이터레이터 패턴

    • GOF : 집합 객체 내부 구조를 노출시키지 않고 순회하는 방법을 제공하는 패턴
      • 집합 객체를 순회하는 클라이언트 코드를 변경하지 않고 다양한 순회 방법을 제공할 수 있음. 
      • 자바에서 사용하는 Iterator가 대표적인 이터레이터 패턴의 구현체가 될 것임.
      • 변하는 것 : Aggregate + Iterator 코드
      • 변하지 않는 것 : Iterator를 순회하는 클라이언트 코드 
      • 다음 경우에 Iterator 패턴 적용을 고려해 볼 수 있음.
        • Aggregate가 다양한 방법으로 순회될 가능성이 있다.
        • Aggregate의 종류가 바뀔 가능성이 있다.  (Map → Tree 등)
    • Component
      • Iterator : 클라이언트가 사용할 인터페이스. 어떻게 순회할지에 대한 방법을 가지고 있음.  getNext(), hasNext()
      • Aggregate : 없어도 무방함. 지금은 Board가 Aggregate에 해당됨.
    • 아래 코드에서는 다음과 같음.
      • Aggregate Interface : List
      • Concrete Aggregate : ArrayList
      • Iterator Interface : Iterator
      • Concrete Iterator : ResentIterator 

     


    디자인 패턴이 필요한 경우

    클라이언트 코드를 살펴보자.

    public class Client {
    
        public static void main(String[] args) {
            Board board = new Board();
            board.addPost("디자인 패턴 게임");
            board.addPost("선생님, 저랑 디자인 패턴 하나 학습하시겠습니까?");
            board.addPost("지금 이 자리에 계신 여러분들은 모두 디자인 패턴을 학습하고 계신 분들입니다.");
    
            // TODO 들어간 순서대로 순회하기
            List<Post> posts = board.getPosts();
            for (int i = 0 ; i < posts.size() ; i++) {
                Post post = posts.get(i);
                System.out.println(post.getTitle());
            }
    
            // TODO 가장 최신 글 먼저 순회하기
            Collections.sort(posts, (p1, p2) -> p2.getCreatedDateTime().compareTo(p1.getCreatedDateTime()));
            for (int i = 0 ; i < posts.size() ; i++) {
                Post post = posts.get(i);
                System.out.println(post.getTitle());
            }
        }
    
    }
    1.  Board 내에는 Post가 추가된다.
    2. 클라이언트는 Post가 추가된 순서대로, Post의 최신순으로 Iteration하면서 결과를 출력한다. 

    평범해 보이는 이 코드에는 어떤 문제점이 존재할까? 

    • Post를 순회할 때, Post가 어떤 Aggregate에 들어있는지 클라이언트가 안다 

    만약 Post가 List안에 들어있었고, 클라이언트가 List 안에 있는 객체를 순회하는 코드를 작성했다고 하자. 그런데 나중에 Post의 Aggregator가 List → Map으로 변경되면 posts.get() 메서드 부분이 변경되어야 할 것이다. 즉, Aggregator를 순회하는 방법이 바뀌어야 한다. 이것은 Aggregate와 Aggregate 순회하는 방법이 캡슐화되지 않았기 때문에 Aggregate가 바뀌면 클라이언트의 코드가 영향을 받는다.

    이것을 개선하기 위해 Iterator 패턴을 적용해 볼 수 있다.

     


    디자인패턴 적용하기

    현재는 클라이언트가 집합 객체 안의 Aggregate가 구체적으로 어떤 클래스인지 알고 있으며, 그 클래스를 직접 가져와서 순회하는 방식이다. 이 부분을 캡슐화 하기 위해서 Iterator 인터페이스를 추가하고, 클라이언트는 Iterator 인터페이스에만 의존하도록 수정할 수 있다. 

    이렇게 변경하게 되면 클라이언트는 집합 객체의 Aggregate가 무엇인지 모르게 되고, 따라서 Aggregate를 순회하는 것도 Iterator 인터페이스에 전적으로 맡길 수 있다. 

    // 최신글부터 순회하는 Iterator
    public class RecentPostIterator implements Iterator<Post> {
        private final Iterator<Post> iterator;
        public RecentPostIterator(List<Post> posts) {
            Collections.sort(posts, (p1, p2) -> p2.getCreatedDateTime().compareTo(p1.getCreatedDateTime()));
            this.iterator = posts.iterator();
        }
    
        @Override
        public boolean hasNext() { return this.iterator.hasNext(); }
    
        @Override
        public Post next() { return this.iterator.next(); }
    }
    
    // 들어간 순서대로 순회하는 Iterator
    public class NaturalOrderIterator implements Iterator<Post> {
        private final Iterator<Post> iterator;
        public NaturalOrderIterator(List<Post> posts) { this.iterator = posts.iterator(); }
    
        @Override
        public boolean hasNext() { return this.iterator.hasNext(); }
    
        @Override
        public Post next() { return this.iterator.next(); }
    }
    • 먼저 Iterator 인터페이스를 구현한 Concrete Iterator 클래스를 구현한다. 
    • 이 때, Iterator를 순회하는 방법이 '최신이냐', '들어간 순서'냐에 영향을 받기 때문에 기본으로 제공되는 Iterator를 사용하되, 정렬만 추가하면 된다. 
    public class Board {
    
        private final List<Post> posts = new ArrayList<>();
    
        public void addPost(String content) { this.posts.add(new Post(content)); }
        
        // Iterator 제공 API
        public Iterator<Post> getRecentPostIterator() { return new RecentPostIterator(this.posts); }
        public Iterator<Post> getNaturalOrderIterator() { return new NaturalOrderIterator(this.posts); }
    }
    • 집합 객체인 Board는 더 이상 구체적인 Aggregate 클래스를 외부에 노출하지 않아도 된다. Iterator를 이용해 캡슐화 하기 때문이다. 
    • 집합 객체 Board는 필요한 Iterator를 제공하도록 public API를 작성한다. 
    public class Client {
    
        private final Iterator<Post> iterator;
    
        public Client(Iterator<Post> iterator) {
            this.iterator = iterator;
        }
    
        public void printPost() {
            while(this.iterator.hasNext()){
                System.out.println(this.iterator.next().getTitle());
            }
        }
    
        public static void main(String[] args) {
            Board board = new Board();
            board.addPost("디자인 패턴 게임");
            board.addPost("선생님, 저랑 디자인 패턴 하나 학습하시겠습니까?");
            board.addPost("지금 이 자리에 계신 여러분들은 모두 디자인 패턴을 학습하고 계신 분들입니다.");
    
            // TODO 들어간 순서대로 순회하기
            Iterator<Post> naturalOrderIterator = board.getNaturalOrderIterator();
            Client client = new Client(naturalOrderIterator);
            client.printPost();
    
            // TODO 가장 최신 글 먼저 순회하기
            Iterator<Post> recentPostIterator = board.getRecentPostIterator();
            Client client1 = new Client(recentPostIterator);
            client1.printPost();
    
        }
    }
    • 클라이언트가 사용할 Iterator를 DI 받는 코드로 작성되어 있다면 이터레이터가 새롭게 추가되거나 바뀌어도 클라이언트쪽 코드는 전혀 변하는 것이 없다. 

    Iterator 인터페이스로 Aggregate 객체와 Aggregate 객체를 순회하는 방법이 추상화되어 있기 때문이다. 이렇게 작성되면 어떤 이점이 있을까?

    • 집합 객체의 Aggregate가 바뀌어도 클라이언트는 영향 받지 않음.
    • Iterator의 순회하는 방법이 바뀌어도 클라이언트는 영향 받지 않음. 
    • 새로운 형태의 Iterator를 추가해도 클라이언트는 영향 받지 않음. 

     


    Iterator 패턴의 장/단점

    • 장점
      • 집합 객체가 가지고 있는 객체들에 손쉽게 접근할 수 있음.
      • 일관된 인터페이스를 사용해 여러 형태의 집합 구조를 순회할 수 있음.
    • 단점
      • 클래스가 늘어나고 복잡도가 증가함. 

    클라이언트는 Iterator 인터페이스에만 의존하고 집합 객체인 Board가 어떤 것인지, Board의 Aggregate가 어떤 것인지 몰라도 된다. 예를 들어 Aggregate 객체가 Tree, List, Heap, Map 어떤 것이라 하더라도 클라이언트 코드가 Aggregate에 영향을 받지 않기 된다. 

    Iterator 패턴을 적용하게 되면 클래스가 늘어나서 코드를 읽기가 어려워 질 수도 있다. 만약 Iterator 패턴을 고려해보고 싶다면 다음 경우가 있는지를 생각해보자.

    • 다양한 방법으로 순회가 될 수 있다.
    • 집합 객체 안의 Collection의 종류가 바뀔 가능성이 있다. 

    댓글

    Designed by JB FACTORY