이터레이터 패턴

GoF의 디자인패턴 중 행동의 이터레이터 패턴을 Java 로 정리한 글

이터레이터 패턴집합 객체 내부 구조를 노출시키지 않고 순회하는 방법을 제공하는 패턴

https://user-images.githubusercontent.com/79291114/150241467-6dfb6efb-36bb-48e1-82d1-96dcc38fcd93.PNG
  • Iterator : 집합체의 요소들을 순서대로 검색하기 위한 인터페이스

  • ConcreteIterator : Iterator 인터페이스를 구현

  • Aggregate : 여러 요소들로 이루어져 있는 집합체

  • ConcreteAggregate : Aggregate 인터페이스를 구현


1. 예제1

요구사항은 다음과 같다.

  • 게시판에 글을 적을 수 있다

  • 게시글을 최근글으로 정렬해서 볼 수 있다

이터레이터 패턴 적용 전 코드

  • 첫번째 for loop를 보면 List 컬렉션의 순서대로 순회해서 작성한 글 순서대로 정렬된다.

  • 두번째 for loop는 최신 글 먼저 순회한다.

문제점

  • Board에 들어간 Post를 순회할 때, Board가 어떠한 구조로 이루어져 있는지를 Client가 알게된다. →메뉴판 제작자(클라이언트)라멘김치찌개를 파는 가게의 메뉴판을 만든다고 가정하겠습니다.메뉴판 제작자(클라이언트)라멘김치찌개를 파는 가게의 메뉴판을 만든다고 가정하겠습니다. BoardPostList로 담겨있다.

  • Board가 자료구조를 List에서 Set이나 Array로 바꾸게 되면 Client 코드도 변경에 영향을 받게 된다. → List인 경우에만 쓰일 수 있음

이터레이터 패턴 적용 후 코드

먼저 간단하게 이터레이터 패턴을 적용한 순회 코드를 보도록 하자

https://user-images.githubusercontent.com/42997924/150263614-8a1e7536-6d73-42da-bc59-c90071a6b6ce.png
https://user-images.githubusercontent.com/42997924/150263487-e4a9b3ef-722d-4059-8243-d632a54a68e4.png

자바에서는 Iterator인터페이스를 기본적으로 제공해주고 있다. 그리고 ArrayList, LinkedListList구현체에는 Iterator가 구현되어있다.

이제 UML다이어그램에 대입해보면 ListAggregate Interface, IteratorIterator Interface가 된다.

posts의 실제 구현체인 ArrayListConcreteAggregate, 그리고 Iterator에 실질적으로 넘어올 수 있는 여러 타입들이 ConcreteIterator가 된다.

출력해서 확인해보면 ArrayListIteratorConcreteIterator이다.

클라이언트 코드는 다음과 같다.

위 코드를 보면 클라이언트는 이제 Iterator 객체를 가지고 hasNext(), next() 메소드만 사용해 순회를 할 수 있게 되었다. 좀 더 클라이언트가 쉽게 사용할 수 있게 하고 쉽다면 아래처럼 Iterator를 반환해주는 메소드를 만들어 줄 수 있다.

이제 요구사항인 최근 글 순서대로 정렬해 순회하는 코드를 보자

위처럼 최근 글로 정렬할 Iterator구현체(ConcretIterator)RecentPostIterator 를 구현해 Board에서 반환해주면 사용자는 이 Iterator가 어떻게 정렬됐는지 알 필요 없이 hasNext(), next()만으로 최근 글로 정렬된 List를 순회할 수 있다.


2. 예제2

메뉴판 제작자(클라이언트)라멘김치찌개를 파는 가게의 메뉴판을 만든다고 가정하자.

이터레이터 패턴 적용 전 코드

위 처럼 식당마다 메뉴관리 방식이 달라 클라이언트 코드에서 메뉴 생성 메소드식당마다 따로 정의해준다. 즉 클라이언트와의 종속성이 높아지게 된다.

이터레이터 패턴 적용 후 코드

이터레이터를 사용하게 되면 클라이언트에서 각 가게의 자료구조를 알 필요 없이, 각 가게의 이터레이터만을 이용해 메뉴판을 제작할 수 있어 종속성이 낮아집니다.

위 클라이언트 코드는 이터레이터 패턴 적용전 코드와 비교하기 위해 메소드 두개를 통해 구현했지만 만약 각 식당이 하나의 Store를 상속받는다면 Store를 순회하면서 Iterator를 호출 시키는 방식으로 더 간단한 코드를 만들 수 있을 것이다.


3. Collection의 구조

아래와 같이 Collection은 Iterable을 상속받기 때문에 Collection 자료구조를 사용하면 Iterator를 사용할 수 있습니다.

하지만 Collection을 상속받지 않는 Map은 자체적으로 Iterator를 사용할 수 없기 때문에 위의 예시와 같이 Map의 Key나 Value를 keySet() 이나 values()를 통해 Collection을 상속받는 Set이나 Collection을 반환 후 Iterator를 사용할 수 있습니다.

https://user-images.githubusercontent.com/58713853/73814627-f84d6400-4826-11ea-95dc-0de32bd8be6c.PNG
https://user-images.githubusercontent.com/79291114/115977688-29918b80-a5b5-11eb-85b1-84c72a9c9be9.png

4. 이터레이드 패턴의 장단점

장점

  • 집합 객체가 가지고 있는 객체들에 손쉽게 접근할 수 있다.

    • Iterator가 제공하는 인터페이스만 알면되고, 집합 객체의 구조는 알 필요없다.

  • 일관된 인터페이스를 사용해 여러 형태의 집합 구조를 순회할 수 있다.

    • 단일책임원칙 - hasNext()와 getNext()만 호출해도 됨.

    • OCP - 기존 코드의 큰 변경 없음 (상황에 따라 변경되기도 함)

단점

  • 클래스가 늘어나고 복잡도가 증가한다.

    • 이터레이터를 만드는 것이 유용한 상황인지 판단할 필요가 있다.

    • 다양한 방법으로 순회하는 방법이 필요하고 내부의 집합 구조가 객체가 변경될 가능성이 있다면, 내부 구조를 클라이언트 쪽에게 숨기는 방법으로 이터레이터 패턴을 적용하는 것이 좋은 방법이 될 수 있다.


5. Java, Spring 적용 사례

자바

  • java.util.Enumeration과 java.util.Iterator

  • Java StAX (Streaming API for XML)의 Iterator 기반 API

    • XmlEventReader, XmlEventWriter

스프링

  • CompositeIterator

자바 - java.util.Enumeration

  • Iterator가 만들어지기 전 java 1.0부터 있었던 API

  • hasMoreElements(), nextElement()

  • Iterator로 거의 대체되었다.

  • java 9 에서는 Iterator로 변환해주는 코드가 추가되었다.

자바 - java.util.Iterator

  • hasNext(), next(), remove(), forEachRemainint()

    • remove() : Iterator에서 next()로 받았던 해당 엘리먼트를 삭제해준다.

      • 모든 이터레이터에서 다 지원하는 것은 아니다.

        • → UnsupportedOperationException()을 던지는 경우가 많다.

      • 보통 이 기능은 Concurrent Modification 동시에 다발적으로 같은 오퍼레이션을 수행해도 안전한 컬렉션에서 제공한다.

    • forEachRemainint()

      • 순회를 좀 더 쉽게 해주도록 도와준다.

      • Consumer 라는 Functional Interface를 파라미터로 받아서 컬렉션을 순회하면서 다 적용해준다.

자바 - Java StAX (Streaming API for XML)의 Iterator 기반 API

  • 자바가 제공해주는 라이브러리

  • XML을 만들거나 읽을 때 사용할 수 있다.

콘솔 기반의 API, 이터레이터 기반의 API를 제공

  • 이터레이터 기반의 API

    • XMl Element 당 이벤트가 지나가면서 캡쳐한다. 해당 영역을 지나가면서 그 영역을 표현하는 XMLEvent라는 인스턴스가 새롭게 만들어진다.

  • 콘솔 기반의 API

    • 커서 기반의 API - 하나의 인스턴스가 Element를 지나가면서 안의 내용들이 갱신되는 방식이다.

콘솔 기반의 API가 메모리 리소스를 덜 사용하므로 더 효율적이다. 하지만, 일반적으로 이터레이터 기반의 API를 사용하는 것을 권장한다. 만들어진 인스턴스를 재사용하거나 변경하는 등의 유연한 처리를 하기 위해서는 (안의 상태가 변경되는 것보다) Immutable하게 하나하나 제 각각의 XML Element를 표현하는 이터레이터 기반의 API가 다루기 용이하다.

이터레이터 기반의 API 코드 예제

  • XmlEventReader, XmlEventWriter

  • Book.xml의 title만 읽어들인다.

  • hasNext(), nextEvent() XMl 엘리먼트를 순회한다.

    • → isStartElement() 시작하는 태그만

    • → nextEvent.asStartElement().getName().getLocalPart().equals("book") 태그 이름이 "book"인 것만

    • → 그 중 "title" 속성값만 출력

참고SAX (Simple API for XML)와 다른 것임. SAX는 XML을 읽기만 가능하다.

스프링 - CompositeIterator

  • 기존의 Interator에 기능만 하나 추가한 것이다.

  • add()

    • 여러 Iterator들을 Composite(조합)해서 사용할 수 있다.

CompositeIterator는 컴포짓 패턴과 겹치는 내용이 있어서 비교하면 좋을 것 같다.

Last updated