[JAVA/디자인패턴] Iterator Pattern, 반복자 패턴
Iterator Pattern
여러 요소들을 담고 있는 객체의 내부 구조에 대한 이해 없이 각 요소를 순서대로 접근하고 사용할 수 있도록 방법을 제공한다.
Collection에서의 Iterator Pattern
사실 우리가 이미 일반적으로 사용하고 있는 Collection의 경우에도 Iterator 패턴이 적용되어 있다.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args){
List<Integer> list = new ArrayList<>();
for(Integer n : list){
System.out.println(n);
}
}
}
다음과 같이 foreach를 통해 ArrayList의 Element를 추출하는 함수를 모르더라도, 리스트 안의 Element를 반복자를 통해 자동으로 추출해도록 설계되어 있다.
실제로 ArrayList의 원시 형태인 Collection 클래스를 살펴보면, 내부적으로 Iterable을 상속받아 구현되어 있는 것을 볼 수 있다.
해당 Iterable 인터페이스를 구현하게 되면, foreach 반복자를 통해서 Iterator에 정의된 순회 로직에 따라 클래스의 반복 형태에 대해 접근할 수 있다.
Iterator의 필요성
각 클래스의 반복자를 직접 정의하여 사용할 수 있다. 다음의 사례를 한번 보자.
public class MyStack<E> {
ArrayList<E> list;
public MyStack(){
this.list = new ArrayList<>();
}
public void push(E e){
this.list.add(e);
}
public E pop(){
int index = this.list.size() - 1;
E element = this.list.get(index);
this.list.remove(index);
return element;
}
}
다음은 자료구조 중 Stack을 구현한 클래스이다.
public class Main {
public static void main(String[] args){
MyStack<Integer> stack = new MyStack<>();
System.out.println("Push 1, 2, 3, 4");
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
// for(int i = 0; i < stack.size(); i++){
// // 클래스이므로 순회 불가능
// }
for(int i = 0; i < stack.list.size(); i++){
// 내부 요소를 알아야 함
}
for(Integer s : stack){
//이렇게 하고 싶음
System.out.println("Print Stack :" + s);
}
}
}
Stack에 Push를 한 후, 내부 Stack에 있는 Element의 요소들을 차례로 출력하고 싶다고 가정하자.
우선 해당 코드의 첫 번째 주석처리되어 있는 반복문은, stack은 List나 Array 등 반복 가능한 Class가 아니므로 사용 불가능하다.
두 번째는, 클래스의 내부 멤버 변수를 알아내서 사용해야한다는 문제점이 있고, private 등 접근 제한이 되어있다면 사용할 수 없다.
Iterator 패턴이 구현되었다면 세 번째처럼 사용할 수 있을 것이고. 지금부터 MyStack 클래스에 Iteraor 패턴을 적용시켜 보자.
커스텀 Iterator 구현
public class MyStackIterator<T> implements Iterator<T> {
ArrayList<T> list;
int idx;
public MyStackIterator(ArrayList<T> list){
this.list = list;
this.idx = 0;
}
@Override
public boolean hasNext() {
return (this.list.size() - 1) >= this.idx;
}
@Override
public T next() {
this.idx++;
return list.get(idx - 1);
}
}
다음 클래스는 MyStack에서 사용할 반복자 패턴을 구현한 클래스이다.
Iterator을 상속받아 요구조건에 맞게 변경한 반복자이며, 생성자로는 ArrayList를 받아, 순회할 대상을 멤버변수로 초기화해주었다.
Iterator를 상속받아 구현해야 할 주요 메서드는 아래의 두 가지다.
boolean hasNext(): 다음에 요소가 존재하는지 여부를 판단하는 메서드이다.
요구사항에 맞게 메서드를 구현하면, 현재 위치(idx)가 리스트의 마지막 요소에 도달하지 않았는지를 확인합니다. 만약 마지막 요소에 도달했다면 false를 반환하고, 그렇지 않으면 true를 반환한다.
T next(): 순회 시 다음 대상이 되는 요소를 반환하는 메서드이다.
현재 위치(idx)를 1 증가시킨 후, 그 위치의 요소를 list에서 가져와 반환한다.
public class MyStack<E> implements Iterable<E> {
ArrayList<E> list;
public MyStack(){
this.list = new ArrayList<>();
}
public void push(E e){
this.list.add(e);
}
public E pop(){
int index = this.list.size() - 1;
E element = this.list.get(index);
this.list.remove(index);
return element;
}
@Override
public Iterator<E> iterator(){
return new MyStackIterator<E>(this.list);
}
}
Java에서 Iterable은 반복문을 통해 자체적으로 순회될 수 있는 객체를 나타내는 인터페이스이다. Iterable 인터페이스를 구현하는 것은 해당 클래스가 반복 가능하다는 것을 나타낸다.
이제 MyStack에 Iterable을 상속시킨 후, 구현해야 할 메서드인 iterator()에 방금 만든 커스텀 Iterator을 반환하도록 한다.
public class Main {
public static void main(String[] args){
MyStack<Integer> stack = new MyStack<>();
System.out.println("Push 1, 2, 3, 4");
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
//while
Iterator iter1 = stack.iterator();
while(iter1.hasNext()){
System.out.println("Print Stack :" + iter1.next());
}
//for
for(Iterator iter2 = stack.iterator(); iter2.hasNext();){
System.out.println("Print Stack :" + iter2.next());
}
//foreach
for(Integer s : stack){
System.out.println("Print Stack :" + s);
}
}
}
이제 반복자 패턴을 구현했다면, MyStack 클래스에 대해 while, for, foreach를 통해 위와 같이 내부 스택 Element를 순회할 수 있다.
'CS > Design pattern' 카테고리의 다른 글
[JAVA/디자인패턴] Factory : 팩토리 메서드 패턴, 추상 팩토리 패턴 (0) | 2023.10.20 |
---|---|
[JAVA/디자인패턴] Decorator Pattern, 데코레이터 패턴 (1) | 2023.10.15 |
[JAVA/디자인패턴] Observer Pattern, 관찰자 패턴 (1) | 2023.10.06 |
[JAVA/디자인패턴] Strategy Pattern, 전략 패턴 (1) | 2023.10.05 |
[JAVA/디자인패턴] Singleton Pattern, 싱글턴 패턴 (0) | 2023.10.05 |