반복자 패턴(Iterator Pattern) ; SRP

반복자 패턴은 컬렉션의 구현 방법을 노출시키지 않으면서도
그 집합체 안에 들어 있는 모든 항목에 접근할 수 있게 해 주는 방법을 제공해줍니다.

단일 책임의 원칙(SRP : Single Responsibility Principle)

클래스를 변경하는 이유가 단 한가지어야 한다.

  • 컬렉션의 기능과 반복하는 책임을 분리한다.
    • 따라서 컬렉션의 기능을 변경한다면, 컬렉션 클래스를 변경하고
    • 반복하는 기능에 대한 변경을 한다면 반복자 클래스를 변경한다

책에서 나오는 예제 - java.util.Iterator 활용

Java Collection 을 크게 나누면 3가지로 나눌 수 있다.

List

순서가 있는 집합체, 중복 허용

1
2
3
4
5
public interface List<E> extends Collection<E> {
...

Iterator<E> iterator();
}

Set

중복을 허용하지 않는 집합

1
2
3
4
5
public interface Set<E> extends Collection<E> {
...

Iterator<E> iterator();
}

Map

<Key, Value> 로 구성되는 집합

  • Key 의 타입은 Set 이다.
  • Value 의 타입은 Collection 이다.
    • 따라서 Value 로 Map 이 다시 등장할 수도 있다.
1
2
3
4
5
6
public interface Map<K,V> {

...

Collection<V> values();
}
1
2
3
4
5
public interface Collection<E> extends Iterable<E> {
...

Iterator<E> iterator();
}

Collection in iterator 결론

컬렉션을 사용한다면 모든 요소에 편하게 접근하는 것을 기대하고 사용하는 경우가 대부분이다.
따라서, 각 구성요소에 반복하며 접근하는 책임을 Iterator 라는 별도 인터페이스로 분리하고,
Collection 의 각 구현체에서 각 자료구조에 따라 반복하는 것에 대한 구현도 담당해야 한다는 것이다.

Spring 속 Iterator Pattern

org.springframework.util.CompositeIterator는 JavaSE의 Iterator 인터페이스의 구현체이다.
Iterator set 을 멤버로 갖고 있으며, 모든 iterator 가 반복 작업을 완료할 때 까지 순차적으로 호출되는 여러 iterator 를 유지 / 관리 한다.
특히, remove 는 다중 스레드 환경에서 심각한 오류를 야기시킬 수 있기 때문에 UnsupportedOperationException를 발생시키는 것이 인상적이다.

컴포지트 패턴으로도 볼 수 있어서 단순히 Iterator Pattern 이라고는 볼 수 없다. 하지만, 중요한 것은 반복에 대한 책임을 가지고 있는 객체라는 점이다.

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package org.springframework.util;

import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.NoSuchElementException;
import java.util.Set;

/**
* Composite iterator that combines multiple other iterators,
* as registered via {@link #add(Iterator)}.
*
* <p>This implementation maintains a linked set of iterators
* which are invoked in sequence until all iterators are exhausted.
*
* @author Erwin Vervaet
* @author Juergen Hoeller
* @since 3.0
* @param <E> the element type
*/
public class CompositeIterator<E> implements Iterator<E> {

private final Set<Iterator<E>> iterators = new LinkedHashSet<>();

private boolean inUse = false;


/**
* Add given iterator to this composite.
*/
public void add(Iterator<E> iterator) {
Assert.state(!this.inUse, "You can no longer add iterators to a composite iterator that's already in use");
if (this.iterators.contains(iterator)) {
throw new IllegalArgumentException("You cannot add the same iterator twice");
}
this.iterators.add(iterator);
}

@Override
public boolean hasNext() {
this.inUse = true;
for (Iterator<E> iterator : this.iterators) {
if (iterator.hasNext()) {
return true;
}
}
return false;
}

@Override
public E next() {
this.inUse = true;
for (Iterator<E> iterator : this.iterators) {
if (iterator.hasNext()) {
return iterator.next();
}
}
throw new NoSuchElementException("All iterators exhausted");
}

@Override
public void remove() {
throw new UnsupportedOperationException("CompositeIterator does not support remove()");
}

}

생각해보자

Java Collection 을 수 없이 사용하면서, Iterator 라는 인터페이스에 대해 생각해 볼 겨를이 없었던 것 같다.
이게 별도 패턴으로 분류되어야 하는가 싶기는 하면서도, 단일 책임의 원칙이라는 관점에서 다시 생각해보면
Iterator 라는 매우 단순하지만 명확한 책임을 가지고 있는 객체도 별로 없는 것 같다는 생각이 든다.

다형성 관점에서의 Iterator

모든 Collection 은 동일한 Iterator 객체를 반환해준다. 따라서 어떤 Collection 을 사용하는 지에 상관없이(List, Set, Map)
‘모든 구성요소에 접근하고 싶다’ 는 목적이면 Iterator 를 반환해서 사용하면 된다.

주의(멀티스레드 환경)

iterator 의 remove()는 신중하게 사용해야 한다. 멀티 스레드 환경에서 동일한 collection 에 대해 remove 는 안전성을 보장하지 않는다.

Java 의 stream 을 통한 반복에서 멀티스레드 안전성에 대해 한번 더 공부해봐야겠다는 생각이 들었다.

참고자료