우리는 다양한 자료구조 형태를 반복문을 사용해서 읽고, 쓰고, 수정해왔습니다. 반복문을 사용하기 위해서 인덱스 변수를 만들고, 배열이나 리스트의 인덱스의 값을 가져왔죠.
하지만 객체 배열이나 리스트가 있다면 어떨까요?
이것도 인덱스를 활용할 수 있겠지만, 객체 내의 값에 직접 접근해야 하는 일이 생길 수 있습니다.
의존성이 높아지는 것이죠.
반복자 패턴 : 컬렉션의 구현 방법을 노출하지 않으면서 집합체 내의 모든 항목에 접근하는 방법을 제공하는 패턴
즉, 자료구조에 상관없이 반복자를 이용해 모든 항목에 접근할 수 있다는 말입니다.
성적을 나열해서 출력하는 클래스를 만든다고 가정해봅시다.
// 반복자 인터페이스입니다. interface Iterator { boolean hasNext(); Score next(); }
반복자는 각 항목에 접근해서 값을 불러옵니다.
// 실제로 반복해줄 구체 반복자입니다. class ScoreIterator implements Iterator { // 성적표로부터 성적 배열을 받습니다. private Score[] score; private int index = 0; ScoreIterator(Score[] score) { this.score = score; } // 다음 인덱스에 값이 있는지 확인합니다. @Override public boolean hasNext() { return this.index < score.length && score[this.index] != null; } // 현재 인덱스의 값을 반환하고 인덱스에 1을 더합니다. @Override public Score next() { return score[this.index++]; } }
정보를 가져올 클래스입니다.
이 클래스에서 정보를 받고, 성적표 클래스로 값을 넘겨줍니다.
class Score{ private int no; private int math; private int java; Score(int no, int math, int java) { this.no = no; this.math = math; this.java = java; } public int getNo() { return this.no; } public int getMath() { return this.math; } public int getJava() { return this.java; } }
성적표 클래스입니다.
addScore()
메소드를 통해 Score
배열에 성적을 저장합니다.
class ScoreSheet { private Score[] scores; private int index = 0; ScoreSheet(int size) { this.scores = new Score[size]; } void addScore(Score score) { this.scores[this.index] = score; this.index++; } // 반복자 객체를 만들고, 반복자에 성적 배열을 넘겨줍니다. public Iterator createIter() { return new ScoreIterator(scores); } }
메인 함수입니다.
public static void main(String[] args) { Score score1 = new Score(1, 90, 100); Score score2 = new Score(2, 100, 50); Score score3 = new Score(3, 60, 80); ScoreSheet scoreSheet = new ScoreSheet(3); scoreSheet.addScore(score1); scoreSheet.addScore(score2); scoreSheet.addScore(score3); Iterator scoreIterator = scoreSheet.createIter(); // 따로 출력만을 전담하는 클래스를 만드는 게 좋습니다. while (scoreIterator.hasNext()) { Score score = scoreIterator.next(); System.out.printf("번호 : %d / 수학 : %d / 자바 : %d\n", score.getNo(), score.getMath(), score.getJava()); } // 번호 : 1 / 수학 : 90 / 자바 : 100 // 번호 : 2 / 수학 : 100 / 자바 : 50 // 번호 : 3 / 수학 : 60 / 자바 : 80 }
반복자는 Score
클래스가 어떤 구조인지 몰라도 됩니다.
어차피 반복자의 역할은 Score
의 객체를 반환하는 것이 끝이거든요.
이렇게하면 각 클래스들은 서로의 구조를 알지 않고도 반복하고, 값에 접근할 수 있게 됩니다.
- 크기가 큰 순회 알고리즘을 별도의 클래스로 추출하여 클라이언트 코드와 컬렉션을 정리함으로 인해 단일 책임 원칙을 만족할 수 있습니다.
- 새로운 유형의 컬렉션 및 반복자를 구현하고 기존의 코드는 수정하지 않음으로써 개방/폐쇄 원칙을 만족할 수 있습니다.
- 각각의 반복자 객체는 그들만의 고유한 반복 상태가 포함되어 있기 때문에 동일한 컬렉션을 병렬로 반복할 수 있습니다.
- 위와 같은 이유로 반복을 지연시킬 수 있고 필요할 때 계속할 수 있습니다.
- 앱이 간단한 컬렉션 만으로도 동작하는 경우 패턴을 적용하는 것이 오히려 지나칠 수 있습니다.
- 반복자를 사용하는 것은 일부 특수 컬렉션의 요소를 직접 탐색하는 것 보다 덜 효율적일 수 있습니다.
https://keencho.github.io/posts/iterator-pattern/
Iterable
인터페이스는 말 그대로 반복할 수 있게
만들어 주는 인터페이스입니다.
Iterable
인터페이스 안에 Iterator
를 반환하는 Iterator()
메소드가 있어 하위 클래스들이
강제로 반복자를 만들도록 했죠.
따라서 이걸 상속받는 컬렉션들, 즉 List
, Queue
, Stack
류 들의 자료구조에서 반복자를 사용할 수 있게 됩니다.
그리고 Iterator
인터페이스는 각 동작이 추상 메소드로 선언되어 있습니다.
hasNext()
, next()
메소드가 선언되어 위의 자료구조들에서 구현되었기 때문에
어떤 자료구조든 상관없이 자유롭게 사용할 수 있게 되었습니다.