디자인 패턴(구)/행위 패턴

반복자(Iterator) 패턴이란?

공대키메라 2022. 4. 21. 23:58

1. 반복자 패턴이란?

의도

내부 표현부를 노출하지 않고 어떤 집합 객체에 속한 원소들을 순차적으로 접근할 수 있는 방법을 제공

 

집합 객체를 순회하는 클라이언트 코드를 변경하지 않고 다양한 순회 방법을 제공할 수 있다.

사용 시기

  • 객체 내부 표현 방식을 모르고도 집합 객체의 각 원소들에 접근하고 싶을 때
  • 집합 객체를 순회하는 다양한 방법을 지원하고 싶을 때
  • 서로 다른 집합 객체 구조에 대해서도 동일한 방법으로 순회하고 싶을 때

구조

  • Iterator : 원소를 접근하고 순회하는 데 필요한 인터페이스를 제공
  • ConcreteIterator : Iterator에 정의된 인터페이스를 구현하는 클래스
  • Aggregate : Iterator 객체를 생성하는 인터페이스를 정의
  • ConcreteAggregate : 해당하는 ConcretIterator의 인스턴스를 반환하는 Iterator 생성 인터페이스를 구현

때에 따라서  Aggregate와 ConcreteAggregate는 존재하지 않을 수 있다고 한다. 

장점

  • 집합 객체가 가지고 있는 객체들에 손쉽게 접근할 수 있다.
  • 일관된 인터페이스를 사용해 여러 형태의 집합 구조를 순회할 수 있다. 

단점

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

이번에는 장점, 단점을 미리 소개했는데 사실 우리는 조금이나마 Iterator 패턴에 대해 이미 알고있다. 

 

그것은 java에서 제공하는 Iterator 때문이다.

Iterator는 자바 컬렉션 프레임워크에서 컬렉션에 저장되어 있는 요소들을 읽어오는 방법을 표준화한 방법중 하나이다. 

 

생각을 해보면 정확히 어떤 것인지는 몰라도 이것을 이용해서 처음부터 끝까지 순회가 가능했다.

 

그러면 이 패턴을 어떻게 구현하는지 보도록 하자.

2. 구현

구현(Implementation) 동화

필자 공대키메라는 같은 반 친구들의 생일을 나열하고 있었다.

쉐인팍... 뱅길타이거... 박사님... 등등... 여러 친구들이 있었다.

그런데 키메라는 전학을 온지 얼마 되지 않아 정확히 몇명이 있는지는 잘 모른다!

다만 학생수가 몇명인지는 몰라도 전부 이름을 외워보고 싶어서 출석부를 펴고 나서, 

하나하나 이름을 외우기 시작했다.

다는 모르지만... 이름부터 차근차근 순회해보자!

Student.java

import lombok.Data;

import java.time.LocalDateTime;

@Data
public class Student {

    private String name;
    private LocalDateTime birthDay;

    public Student(String name) {
        this.name = name;
        this.birthDay = birthDay.now();
    }

}

Classroom.java

import lombok.Data;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

@Data
public class Classroom {
    List<Student> students = new ArrayList<>();

    public void addStudent(String name){
        this.students.add(new Student(name));
    }

    public Iterator<Student> getNameSortStudentIterator (){
        return new NameSortStudentIterator(this.students);
    }
}

NameSortStudentIterator.java

import java.util.Collections;
import java.util.Iterator;
import java.util.List;

public class NameSortStudentIterator implements Iterator<Student> {

    private Iterator<Student> internalIterator;

    public NameSortStudentIterator(List<Student> students) {
        Collections.sort(students, (p1, p2) -> p2.getName().compareTo(p1.getName()));
        this.internalIterator = students.iterator();
    }

    @Override
    public boolean hasNext() {
        return this.internalIterator.hasNext();
    }

    @Override
    public Student next() {
        return this.internalIterator.next();
    }

}

Main.java

import java.util.Iterator;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        Classroom classroom = new Classroom();
        classroom.addStudent("shane");
        classroom.addStudent("bengal tiger");
        classroom.addStudent("doctor kim");
        classroom.addStudent("chimera");

        List<Student> students = classroom.getStudents();
        Iterator<Student> iterator = students.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next().getName());
        }

        Iterator<Student> studentList = classroom.getNameSortStudentIterator();
        for (Student student : students) {
            System.out.println("student = " + student.getName());
        }
    }
}

 

출력 결과

더보기

shane
bengal tiger
doctor kim
chimera


student = shane
student = doctor kim
student = chimera
student = bengal tiger

 

여기서 다시 한번 반복자 패턴의 의도를 생각해보자.

 

내부 표현부를 노출하지 않고 어떤 집합 객체에 속한 원소들을 순차적으로 접근할 수 있는 방법을 제공하는 것이 반복자 패턴이다.

 

Main.java에서 보면 처음에 단순히 출력했을 때는 무언가... 너무 당연한듯한 출력이지만

 

getNameSortStudentIterator를 classroom에서 가져오는 순간 우리는 return값을 받아서 iterator로 출력했을 뿐이다.

 

즉, 실제로 코드를 까보지 않는 이상 알 필요가 없다는 것이다. 

 

java에서 지원해주는 Iterator 인터페이스를 이용해서 구현을 하면

 

우리가 배우고자 하는 Iterator 패턴을 손쉽게 구현할 수 있다.

 


이게 Iterator 패턴의 전부이다.

 

구조는 사실 구조일 뿐, 그거보다 더 중요한 것은 그 패턴을 사용하는 의도이다. 

 

구조적으로 완벽하게 구성이 되지는 않았지만, 반복자 패턴의 목적을 충실히 따랐다.

 

즉, 내부 표현부를 노출하지 않고 어떤 집합 객체에 속한 원소들을 순차적으로 접근할 수 있는 방법을 제공했다.

 

너무 간단하고 별거 없지만 의도는 꼭 기억하고가자.