본문 바로가기
SW

GoF의 디자인 패턴(Design Patterns: Elements of Reusable Object-Oriented Software) - 5장 행동패턴 :: 반복자(Iterator)

by 라꾸스떼(YR) 2020. 5. 7.
반응형

[반복자(Iterator) - 객체 행동]

<의도>

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

<다른 이름>

커서(Cursor)

<동기>

이 패턴의 목적은 리스트 객체(집합 객체-aggregate object)에 접근해서 새로운 내용을 삽입, 삭제하거나 순회하는 내용을 반복자 객체에 정의하는 것이다. 반복자 객체를 나타내는 iterator 클래스는 리스트의 원소들에 접근하는 데 필요한 인터페이스를 제공한다.

ListIterator 클래스의 인스턴스를 생성하기 전에 먼저 순회 주체가 되는 List 객체를 생성해야 한다. ListIterator 클래스의 인스턴스를 생성하고 나면 이를 이용해서 리스트 원소에 접근할 수 있게 된다.

CurrentItem() : 리스트의 현재 원소를 알아내는 연산

First() : 현재 원소를 리스트 첫 번째 원소로 초기화하는 연산

Next() : 다음 원소를 순회 과정 중의 현재 원소로 지정하는 연산

isDone() : 순회할 원소가 더 있는지 없는지 확인하는 연산

List의 복합 구조를 변경하더라도 사용자 코드는 변경하지 않도록 만들면 더욱 좋다. 이렇게 하려면, 반복자의 개념을 일반화하여 다형성을 지닌 반복(polymorphic iteration)이 가능하도록 하면 된다.

<활용성>

-객체 내부 표현 방식을 모르고도 집합 객체의 각 원소들에 접근하고 싶을 때

-집합 객체를 순회하는 다양한 방법을 지원하고 싶을 때

-서로 다른 집합 객체 구조에 대해서도 동일한 방법으로 순회하고 싶을 때

<구조>

<참여자>

-Iterator : 원소를 접근하고 순회하는 데 필요한 인터페이스를 제공한다.

-ConcreteIterator : Iterator에 정의된 인터페이스를 구현하는 클래스로, 순회 과정 중 집합 객체 내에서 현재 위치를 기억한다.

-Aggregate : Iterator 객체를 생성하는 인터페이스를 정의한다.

-ConcreteAggregate : 해당하는 ConcreteIterator의 인스턴스를 반환하는 Iterator 생성 인터페이스를 구현한다.

<협력 방법>

ConcreteIterator는 집합 객체 내 현재 객체를 계속 추적하고 다음번 방문할 객체를 결정한다.

<결과>

1.집합 객체의 다양한 순회 방법을 제공한다.

2.Iterator는 Aggregate 클래스의 인터페이스를 단순화한다.

3.집합 객체에 따라 하나 이상의 순회 방법이 제공될 수 있다.

<구현>

1.누가 반복을 제어하게 할까요? 사용자가 반복을 제어할 때, 이 반복자는 외부 반복자라고 한다. 반복자 자신이 제어를 담당한다면 내부 반복자라고 한다. 외부 반복자를 사용하는 사용자 프로그램은 순회를 계속하고 다음번 원소를 명시적으로 반복자에게 요청해야 한다. 이와는 달리 사용자가 내부 반복자를 사용할 때는 처리할 연산을 내부 반복자에게 넘겨주고, 해당 반복자는 그 연산을 모든 원소에 적용한다. 외부 반복자가 내부 반복자보다는 유연한 방법이다.

C++는 표준 템플릿 라이브러리(STL)로 컨테이너를 순회할 수 있는 반복자 개념을 기본으로 제공한다.

2.순회 알고리즘을 어디에서 정의할 것인가?

3.어떻게 반복자를 견고하게 만들 수 있을까? 견고한 반복자(robust iterator)가 되려면 순회 중에는 삽입이나 삭제가 일어나지 말아야 하고, 또 집합 객체를 복사하는 방법을 사용하지 않아야 한다. 이렇게 구현하는 방법은 집합 객체에 반복자를 등록하는 방식을 취한다. 삽입이나 삭제를 할 때 집합 객체가 자신이 보유한 반복자의 상태도 함께 변경할 수 있게 하는 것이다.

4.추가적으로 필요한 반복자 연산. Iterator 클래스에 필요한 최소한의 연산들 : First(), Next(), IsDone(), CurrentItem(), Previous(), SkipTo()

5.C++에서 다향성을 지닌 반복자를 이용하는 방법. 다형적인 반복자는 런타임에 팩토리 메서드로 동적으로 반복자 객체를 제공해야 하기 때문에 추가 비용을 지불해야 한다. 다형적인 반복자의 단점 중 하나는 사용자가 직접 반복자를 삭제하는 책임을 져야 한다는 것이다. 프록시 패턴을 쓰면 문제를 해결할 수 있는데, 실제로 스택에 저장한 프록시를 반복자처럼 사용하는 것이다. 프록시는 자신의 소멸자에서 프록시를 삭제하면 된다.

6.반복자에는 특수한 접근 권한이 있다. 밀접한 관계를 C++로 구현한다면, 집합 객체를 정의할 때 반복자를 그 집합 구조의 friend로 정의할 수 있다. 집합 객체에 정의된 멤버 변수 중에서 중요하기는 하지만 공개할 수 없는 멤버 변수에 접근하는 연산을 Iterator 클래스 안에다가 protected로 정의하는 것이다.

7.복합체를 위한 반복자. 일반적으로 사용하는 순회 알고리즘으로는 전위 순회법, 후위 순회법, 중위 순회법, 너비 우선 탐색 등이 있다.

8.널 반복자(NullIterator). 널 반복자는 항상 순회 시에 끝나는 반복자로 정의된다. 다시 말해, NullIterator는 IsDone() 연산으로 항상 참을 평가한다.

<예제 코드>

1.List 및 Iterator 인터페이스

2.Iterator 서브클래스 구현

3.Iterator 사용

4.리스트 구현이 표준에서 벗어나는 상황 방지

5.Iterator를 확실히 삭제

6.내부 ListIterator 보기

<잘 알려진 사용예>

<관련 패턴>

반복자 패턴은 복합체 패턴과 같이 재귀적 구조가 있을 때 자주 사용한다. 다양한 반복자를 사용해서 적당한 Iterator 서브클래스를 얻으려면 팩토리 메서드 패턴을 사용할 수 있다. 메멘토 패턴도 반복자 패턴과 함께 자주 사용하는데, 이때 반복자 자신이 반복한 결과를 저장하기 위해서 메멘토를 사용한다.

반응형

댓글