책정리/GoF 디자인 패턴

20장 동일 자료형의 여러 객체에 대한 순차적 접근 문제(Iterator패턴)

GONII 2019. 2. 6. 03:56
    프로그램을 작성하다 보면 여러 개로 구성되는 자료구조에 대해 각각의 구성 요소를 접근해서 필요한 작업을 수행해주어야 하는 경우가 종종 발생하게 된다. 예를 들어 배열의 구성 요소를 찾아다니면서 일괄적으로 작업을 처리한다든지, 트리를 구성하는 노드들을 방문해서 작업을 수행하는 경우가 그것이다.
    같은 작업을 위해 대부분 for while 같은 반복 문장을 수행하게 된다. 그런데 과정에서 문제는 자료구조마다 개별 구성 요소를 접근하기 위한 방법이 다르기 때문에 항상 각각의 자료구조에 맞추어 개별 구성 요소를 접근하는 프로그램을 따로 작성해야 한다는 점이다. 예를 들어 배열의 경우에는 인덱스 값을 증가시켜나가면서 개별 구성 요소를 접근해야 하는 반면, 트리의 경우에는 일일이 자식 노드나 형제 노드에 대한 포인터를 따라가야 한다. 각각의 자료구조마다 개별 구성 요소를 순차적으로 접근하기 위한 프로그램 모듈을 별도로 작성해야 한다는 것은 Client 입장에서 너무 불편하고 지저분한 작업이다.
    장에서는 동일 자료형의 여러 객체가 모여 있는 자료구조에 대해 각각의 구성 요소를 순차적으로 접근하기 위한 해결책을 살펴본다.
    • 문제 사례 설명
    리스트나 트리를 구성하는 객체들에 대해 일정한 작업을 수행하고자 한다. 이런 작업을 위해서는 리스트나 트리를 구성하는 객체들을 순차적으로 접근할 필요가 있을 것이다.
    • 다양한 접근 방법 및 ITERATOR 패턴
    리스트를 구성하는 항목들을 순차적으로 접근하기 위한 방법을 찾을 것이다.
    리스트를 구성하는 항목들이 다음 항목을 가리키게 포인터를 두고 이를 통해 리스트 내의 항목들을 차례로 접근하였었다. 그러나 방법은 리스트 내의 항목들이 어떤 형태의 자료구조를 가졌는지 미리 알고 있어야 적용 가능한 것이다.
    그렇다면 리스트 내부의 자료구조와 무관하게 리스트 내의 항목들을 차례로 접근할 있는 객체지향적 방법은 무엇일까?
    • 기본적인 방법: 리스트 자체를 클래스로 정의하는 방법
    구조적 방법이 외부에서 리스트 내부의 자료구조를 직접 사용했던 단점을 보완하기 위해 리스트 자체를 클래스로 정의하고, 내부의 자료 구조를 외부에 숨기도록 만드는 것이다. 이런 생각에 따라 리스트 자체를 클래스로 정의하게 되면 [그림 20-1] 같은 형태로 만들어질 것이다.


    그런데 [그림 20-1] 같이 List 클래스만 정의해서 리스트를 구성하는 항목들을 순차적으로 접근하고자 경우 가지 다른 문제가 발생할 있다.
    먼저 List 클래스의 인터페이스가 너무 많고 복잡해진다는 것이다. 이는 List 클래스가 개별 항목들을 추가하거나 삭제하는 인터페이스도 제공해야 하지만, 리스트를 구성하는 항목들을 순차적으로 이동하면서 접근하기 위한 인터페이스도 제공해야 하기 때문이다. 이처럼 하나의 클래스에 여러 종류의 인터페이스가 모여있게 되면 클래스 자체의 구현도 어려워질 뿐만 아니라 상속 등을 위한 클래스 확장도 힘들다는 문제가 발생한다.
    가장 문제가 있는 것은 개의 List 클래스 객체에 대해 동시에 이상 다른 위치의 항목을 접근하는 것이 불가능하다는 점이다. 예를 들어 리스트 내의 항목 서로 같은 자료 값을 가지고 있는 항목이 있는지를 비교한다고 해보자. 이를 위해서는 동일한 List 클래스 객체에 대해 서로 다른 위치의 항목들을 동시에 접근해서 비교할 있는 방법이 있어야 것이다. 그러나 [그림 20-1] 같이 클래스가 정의된 경우에는 하나의 List 클래스 객체 내부에는 현재 위치 정보 개만 관리되기 때문에 이상의 위치 정보를 필요로 하는 같은 작업 수행은 불가능하다는 문제가 발생한다.
    • 패턴 활용 방식: ITERATOR 패턴
    우선 동일한 리스트에 대해 서로 다른 위치를 동시에 접근해서 작업을 수행할 있는 방법에 대해 생각해 보자. [그림 20-1]에서처럼 List 클래스 내부에 현재 접근하고 있는 위치 정보를 저장, 관리하는 방식은 동일한 List 객체에 대해 서로 다른 위치 정보를 가질 없는 문제가 있다. 왜냐하면 하나의 List 클래스 객체는 개의 위치 정보만 저장, 관리할 있기 때문이다.
    따라서 동일한 List 객체에 대해 동시에 서로 다른 위치 정보를 가질 있게 하려면 위치 정보를 저장, 관리하기 위한 객체를 별도로 분리하는 것이 바람직하다. 이를 위한 객체의 클래스를 ListIterator라고 하면 [그림 20-2] 같은 형태의 클래스 설계가 가능할 것이다.
    그림에서 보는 것처럼 List클래스에 집중되어 있던 인터페이스도 자연스럽게 분리가 이루어질 있다.


    한편 [그림 20-2]에서 ListIterator 클래스는 객체 생성 반드시 List 클래스의 객체가 지정되도록 하는 것이 좋다. 왜냐하면 ListIterator 객체는 List 객체에 대해 구성 항목들의 위치 정보를 나타내는 것이므로, List 객체가 지정되지 않은 상태에서는 존재할 이유가 없기 때문이다. 또한 ListIterator 클래스는 List 객체의 항목으로 현재 위치를 옮기기 위한 First() 현재 위치를 다음 항목으로 이동시키기 위한 Next(), 마지막 항목까지 도달하였는지 확인하기 위한 IsDone(), 형재 가리키고 있는 항목을 되돌리기 위한 GetCurItem() 같은 인터페이스가 제공되야 한다.
    [소스 20-1] ListIterator 클래스 인터페이스 사용

    ListIterator* iter = (ListIterator*) list.CreateIterator();
    for (iter->First(); !iter->IsDone(); iter->next())
    cout << iter->GetCurItem()->data_ << endl;
    실제 리스트를 구성하는 항목을 관리하기 위한 List 클래스와는 별도로 List 클래스 객체 내의 항목들을 순차적으로 접근하기 위한 클래스를 정의해서 사용하는 방식을 Iterator 패턴이라고 한다.
    Iterator 패턴을 사용할 경우 리스트를 구성하는 구체적인 자료구조가 List 클래스 객체 내로 숨겨질 뿐만 아니라 동일한 리스트의 서로 다른 위치를 동시에 접근하는 것이 가능하다. 왜냐하면 동시에 접근할 개수만큼 ListIterator 클래스의 객체를 생성하면 되기 때문이다.
    밖에도 Iterator 패턴은 리스트 내의 항목들을 접근하는 정책을 바꾸고 싶을 때도 유용하다. 예를 들어 리스트 내에서 특정 항목들만 찾아서 접근하고 싶을 경우에는 FilteringListIterator 같은 클래스를 정의해서 사용하면 List 클래스 자체를 수정할 필요없이 원하는 작업을 수행할 있기 때문이다.
    트리와 같은 자료구조에 대해서도 트리를 구성하는 항목을 순차적으로 접근할 있는 TreeIterator 클래스를 [그림 20-3] 같이 정의할 있다.


    [그림 20-3] 살펴보면 개별 클래스들의 상위에 AbstractList Iterator라는 Abstract Base Class 정의하고 있다. 이는 클래스들이 따로 따로 정의될 경우 일관된 인터페이스가 유지되지 않는 문제를 해결해주기 위한 것이다.
    AbstractList 클래스는 CreateIterator() 라는 Factory Method 가지고 있다. 이를 통해 AbstractList 클래스 하위 클래스들은 각각 자신에게 적당한 Iterator 클래스 객체를 생성하는 책임을 가진다. 이때 Iterator 객체 생성 지정해줄 리스트 객체는 바로 자기 자신이 것이다.
    • 샘플 코드
    [그림 20-3]에서 보는 바와 같이 LiskedList TreeList 클래스와 ListIterator TreeIterator 클래스는 서로 순환 참조를 하고 있다. 순환 참조를 하는 경우 전체 프로그램을 하나의 파일로 작성해서는 컴파일 없다.
    같은 문제를 해결하기 위해서는 LinkedList TreeList 클래스를 위한 헤더 파일과 구현 파일을 따로 작성하고, ListIterator TreeIterator 클래스를 위한 헤더 파일과 구현 파일을 따로 작성해야 한다. 헤더파일 간에도 순환 참조가 일어나지 않게 한쪽 헤더 파일에서 상대방 헤더 파일을 직접 include하지 말고 클래스명만 미리 선언하는 방식을 취해야 한다.
    [소스 20-2] LinkedList TreeList 대한 Iterator 패턴
    // item.h 파일
    #ifndef ITEM_H
    #define ITEM_H
     
    #include <string>
    using namespace std;
     
    class Item
    {
    public:
    Item(string str)
    : data_(str)
    {}
    string data_;
    };
     
    #endif
     
    // list.h 파일
    #ifndef LINKED_TREE_LIST_H
    #define LINKED_TREE_LIST_H
     
    #include "item.h"
    class Iterator;
     
    class AbstractList
    {
    public:
    virtual Iterator* CreateIterator() = 0;
    int Count();
    virtual void AddNext(Item* pNewItem, Item* pItem = 0) = 0;
    virtual void AddChild(Item* pNewItem, Item* pItem) = 0;
    virtual void Remove(Item* pItem) = 0;
    virtual Item* GetItem(int pos) = 0;
    protected:
    AbstractList()
    :totalCnt_(0)
    {}
    int totalCnt_;
    };
     
    class LinkedList : public AbstractList
    {
    public:
    LinkedList()
    : pFirst_(0)
    {}
     
    struct LinkedItem
    {
    LinkedItem(Item* pItem =0, LinkedItem* pNext=0)
    : pData_(pItem), pNext_(pNext)
    {}
    Item* pData_;
    LinkedItem* pNext_;
    };
     
    Iterator* CreateIterator();
    void AddNext(Item* pNewItem, Item* pItem = 0);
    void AddChild(Item* pNewItem, Item* pItem = 0);
    void Remove(Item* pItem);
    Item* GetItem(int pos);
    private:
    LinkedItem* pFirst_;
    };
     
    class TreeList : public AbstractList
    {
    public:
    TreeList()
    : pRoot_(0)
    {}
     
    struct TreeItem
    {
    TreeItem(Item* pItem = 0, TreeItem* pParent = 0, TreeItem* pFirstChild = 0, TreeItem* pSibling = 0)
    : pData_(pItem), pParent_(pParent), pFirstChild_(pFirstChild), pSibling_(pSibling)
    {}
    Item* pData_;
    TreeItem* pParent_;
    TreeItem* pFirstChild_;
    TreeItem* pSibling_;
    };
     
    Iterator* CreateIterator();
    void AddNext(Item* pNewItem, Item* pItem = 0);
    void AddChild(Item* pNewItem, Item* pItem = 0);
    void Remove(Item* pItem);
    Item* GetItem(int pos);
    protected:
    TreeItem* GetNext(const TreeItem* pStart);
    private:
    TreeItem* pRoot_;
    };
     
    #endif
     
    // list.c파일
    #include <iostream>
    #include "list.h"
    #include "iter.h"
    using namespace std;
     
    int AbstractList::Count()
    {
    return totalCnt_;
    }
     
    //------------------------------
     
    Iterator* LinkedList::CreateIterator()
    {
    return new LinkedIterator(this);
    }
     
    void LinkedList::AddNext(Item* pNewItem, Item* pItem)
    {
    if (pFirst_ == 0)
    {
    pFirst_ = new LinkedItem(pNewItem);
    }
    else if (pItem == 0 || pItem == pFirst_->pData_)
    {
    LinkedItem* pTemp = pFirst_->pNext_;
    pFirst_->pNext_ = new LinkedItem(pNewItem, pTemp);
    }
    else
    {
    LinkedItem* pPrev = 0;
    LinkedItem* pTemp = pFirst_;
    while (pTemp != 0 && pTemp->pData_ != pItem)
    {
    pPrev = pTemp;
    pTemp = pTemp->pNext_;
    }
     
    if (pTemp != 0)
    {
    LinkedItem* pTemp2 = pTemp->pNext_;
    pTemp->pNext_ = new LinkedItem(pNewItem, pTemp2);
    }
    else
    pPrev->pNext_ = new LinkedItem(pNewItem, 0);
    }
    totalCnt_++;
    }
     
    void LinkedList::AddChild(Item* pNewItem, Item* pItem)
    {
    AddNext(pNewItem, pItem);
    }
     
    void LinkedList::Remove(Item* pItem)
    {
    if (pItem == 0)
    return;
     
    LinkedItem* pPrev = 0;
    LinkedItem* pTemp = pFirst_;
     
    while (pTemp != 0 && pTemp->pData_ != pItem)
    {
    pPrev = pTemp;
    pTemp = pTemp->pNext_;
    }
     
    if (pTemp != 0)
    {
    if (pTemp == pFirst_)
    {
    delete pTemp;
    pFirst_ = 0;
    }
    else
    {
    pPrev->pNext_ = pTemp->pNext_;
    delete pTemp;
    }
    totalCnt_--;
    }
    }
     
    Item* LinkedList::GetItem(int pos)
    {
    int cnt = 0;
    LinkedItem* pTemp = pFirst_;
     
    while (pTemp != 0 && cnt != pos)
    {
    pTemp = pTemp->pNext_;
    cnt++;
    }
     
    if (pTemp != 0)
    return pTemp->pData_;
    else
    return 0;
    }
    //-------------------------------------
     
    void TreeList::AddNext(Item* pNewItem, Item* pItem)
    {
    if (pRoot_ == 0)
    pRoot_ = new TreeItem(pNewItem);
    else if (pItem == 0 || pItem == pRoot_->pData_)
    {
    TreeItem* pTemp = pRoot_->pFirstChild_;
    pRoot_->pFirstChild_ = new TreeItem(pNewItem, pRoot_, 0, pTemp);
    }
    else
    {
    TreeItem* pPrev = 0;
    TreeItem* pTemp = GetNext(pRoot_);
    while (pTemp != 0 && pTemp->pData_ != pItem)
    {
    pPrev = pTemp;
    pTemp = GetNext(pTemp);
    }
     
    if (pTemp != 0)
    {
    TreeItem* pTemp2 = pTemp->pSibling_;
    pTemp->pSibling_ = new TreeItem(pNewItem, pTemp->pParent_, 0, pTemp2);
    }
    else
    pPrev->pSibling_ = new TreeItem(pNewItem, pPrev->pParent_, 0, 0);
    }
    totalCnt_++;
    }
     
    void TreeList::AddChild(Item* pNewItem, Item* pItem)
    {
    if (pRoot_ == 0)
    pRoot_ = new TreeItem(pNewItem);
    else if (pItem == 0 || pItem == pRoot_->pData_)
    {
    TreeItem* pTemp = pRoot_->pFirstChild_;
    pRoot_->pFirstChild_ = new TreeItem(pNewItem, pRoot_, 0, pTemp);
    }
    else
    {
    TreeItem* pPrev = 0;
    TreeItem* pTemp = GetNext(pRoot_);
     
    while (pTemp != 0 && pTemp->pData_ != pItem)
    {
    pPrev = pTemp;
    pTemp = GetNext(pTemp);
    }
     
    if (pTemp != 0)
    {
    TreeItem* pTemp2 = pTemp->pFirstChild_;
    pTemp->pFirstChild_ = new TreeItem(pNewItem, pTemp, 0, pTemp2);
    }
    else
    pPrev->pFirstChild_ = new TreeItem(pNewItem, pPrev, 0, 0);
    }
    totalCnt_++;
    }
     
    void TreeList::Remove(Item* pItem)
    {
    if (pItem == 0)
    return;
     
    TreeItem* pPrev = 0;
    TreeItem* pTemp = pRoot_;
     
    while (pTemp != 0 && pTemp->pData_ != pItem)
    {
    pPrev = pTemp;
    pTemp = GetNext(pTemp);
    }
     
    if (pTemp != 0)
    {
    if (pTemp->pFirstChild_ != 0)
    cout << "Can't Remove the requested Node" << endl;
    else
    {
    if (pTemp == pRoot_)
    {
    delete pTemp;
    pRoot_ = 0;
    }
    else if(pTemp->pParent_->pFirstChild_ == pTemp)
    {
    pTemp->pParent_->pFirstChild_ = GetNext(pTemp);
    delete pTemp;
    }
    else
    {
    pPrev->pSibling_ = pTemp->pSibling_;
    delete pTemp;
    }
    totalCnt_--;
    }
    }
    }
     
    Item* TreeList::GetItem(int pos)
    {
    int cnt = 0;
    TreeItem* pTemp = pRoot_;
     
    while (pTemp != 0 && cnt != pos)
    {
    pTemp = GetNext(pTemp);
    cnt++;
    }
     
    if (pTemp != 0)
    return pTemp->pData_;
    else
    return 0;
    }
     
    TreeList::TreeItem* TreeList::GetNext(const TreeItem* pStart)
    {
    if (pStart == 0)
    return 0;
    else if (pStart->pSibling_ != 0)
    return pStart->pSibling_;
    else if (pStart->pParent_ == 0)
    return pStart->pFirstChild_;
    else
    return pStart->pParent_->pFirstChild_->pFirstChild_;
    }
     
    // iter.h 파일
    #ifndef ITER_H
    #define ITER_H
     
    #include "item.h"
     
    class LinkedList;
    class TreeList;
     
    class Iterator
    {
    public:
    void First();
    void Next();
    virtual bool IsDone() = 0;
    virtual Item* GetCurItem() = 0;
    protected:
    int curPos_;
    Iterator()
    {
    curPos_ = 0;
    }
    };
     
    class ListIterator : public Iterator
    {
    public:
    ListIterator(LinkedList* pList);
     
    bool IsDone();
    Item* GetCurItem();
     
    private:
    LinkedList* pList_;
    };
     
    class TreeIterator : public Iterator
    {
    public:
    TreeIterator(TreeList* pTree);
     
    bool IsDone();
    Item* GetCurItem();
    private:
    TreeList* pTree_;
    };
     
    #endif
     
    // iter.c 파일
    #include <iostream>
    #include "iter.h"
    #include "list.h"
    using namespace std;
     
    void Iterator::First()
    {
    curPos_ = 0;
    }
     
    void Iterator::Next()
    {
    curPos_++;
    }
    //-------------------------
     
    ListIterator::ListIterator(LinkedList* pList)
    {
    pList_ = pList;
    }
     
    bool ListIterator::IsDone()
    {
    if (curPos_ >= pList_->Count())
    return true;
    else
    return false;
    }
     
    Item* ListIterator::GetCurItem()
    {
    return pList_->GetItem(curPos_);
    }
    //----------------------------
    TreeIterator::TreeIterator(TreeList* pTree)
    {
    pTree_ = pTree;
    }
     
    bool TreeIterator::IsDone()
    {
    if (curPos_ >= pTree_->Count())
    return true;
    else
    return false;
    }
     
    Item* TreeIterator::GetCurItem()
    {
    return pTree_->GetItem(curPos_);
    }
     
    Item* TreeIterator::GetCurItem()
    {
    return pTree_->GetItem(curPos_);
    }
     
    // main.c 파일
    #include <iostream>
    #include "item.h"
    #include "list.h"
    #include "iter.h"
    using namespace std;
     
    void PrintDat(Iterator* pIter)
    {
    for (pIter->First(); !pIter->IsDone(); pIter->Next())
    cout << pIter->GetCurItem()->data_ << endl;
    }
     
    void main()
    {
    Item item1("A"), item2("B"), item3("C"), item4("D");
     
    LinkedList list;
    list.AddNext(&item1);
    list.AddNext(&item2, &item1);
    list.AddNext(&item3, &item2);
    list.AddNext(&item4, &item3);
     
    ListIterator* pListIter = (ListIterator*)list.CreateIterator();
    cout << "--------------------------------" << endl;
     
    TreeList tree;
    tree.AddNext(&item1);
    tree.AddNext(&item2, &item1);
    tree.AddChild(&item3, &item2);
    tree.AddChild(&item4, &item2);
     
    TreeIterator* pTreeIter = (TreeIterator*)tree.CreateIterator();
    PrintDat(pListIter);
    }
    • 구현 관련 사항
    누가 리스트나 트리 객체 내의 항목에 대한 접근을 제어할 것인지에 따라 Iterator 가지로 구분된다. 이중 External Iterator Client 항목에 대한 접근을 수행하는 것이다. , Client 항목들을 순차적으로 가져와서 원하는 작업을 수행하는 형태를 가리킨다. 반면 Internal Iterator Iterator 자체적으로 항목들을 순차적으로 접근해서 작업을 수행하는 형태다. 이때 Client Iterator 수행할 작업에 대한 정보만을 전달한다.
    이와 같은 가지 형태의 Iterator External Iterator 훨씬 유연하다. 왜냐하면 External Iterator 경우에는 Client 임의로 접근 순서를 제어할 있기 때문이다. 예를 들어 개의 리스트가 동일한지 여부를 비교하는 작업은 External Iterator 의해서만 구현이 가능하다. 왜냐하면 Internal Iterator 경우에는 Iterator 객체 내부에서 항목에 대한 접근을 수행하기 때문에 Iterator 객체 외부에서 항목들을 서로 비교하는 것이 불가능하기 때문이다.
    Internal Iterator 유연하지도 않고, 구현하기도 적절하지 않음에도 불구하고 이를 사용하는 이유는 Client 요청하는 작업의 종류가 바뀌더라도 리스트나 트리 내의 항목들을 접근하는 로직을 재사용 가능하기 때문이다. 각각의 작업 수행 시마다 리스트나 트리 객체 내의 항목들을 접근하기 위한 프로그램 모듈을 별도로 작성할 필요없이 원하는 작업 정보만 넘겨 주면 작업 수행이 가능하기 때문이다.
    다음으로 Iterator 구현과 관련해서 생각해볼 사항은 리스트나 트리 객체 내의 항목을 접근하는 실제 알고리즘은 어디에 두는 것이 좋을지에 대한 것이다.
    가지 방법은 [소스 20-2]에서처럼 리스트나 트리 객체 내의 항목을 접근하기 위한 알고리즘을 두고 Iterator 객체에는 현재 위치 정보만 기억시키는 방식이다. 같은 경우의 Iterator 특별히 Cursor라고 부르는데, 이는 Iterator 객체가 리스트나 트리 객체 내의 위치 정보만 가지고 있기 때문이다.
    다른 방법으로는 Iterator 클래스 내에 리스트나 트리 객체 내의 항목들을 접근하는 알고리즘을 두는 방식이다. 방식은 항목들을 접근하는 알고리즘을 변경하고자 경우 Iterator클래스의 하위에 새로운 클래스를 정의하고 원하는 알고리즘을 구현하면 되므로, 리스트나 트리 객체 내의 항목을 접근하는 방식을 변경시키는 것이 쉬운 장점이 있다.
    Iterator 패턴의 구현과 관련해서 또하나 생각해볼만한 것은 Iterator 객체에 의해 리스트나 트리 내의 항목들이 사용되고 있는 도중에 새로운 항목이 추가된다든지 기존 항목이 삭제되면 어떻게 것인가 하는 점이다.
    경우 Iterator 객체는 동일한 항목을 접근하거나 특정 항목을 빠뜨릴 가능성이 있다. 이런 문제를 완벽히 해결할 있는 방법은 Iterator 객체가 생성될 Iterator 객체에게도 반영시켜주는 방식이 보다 현실적이라고 있다. 예를 들어 [소스 20-2]에서 IsDone() 멤버 함수는 현재 위치 정보인 curPos_ 값이 리스트나 트리 객체의 항목 개수보다 크거나 같으면 참을 되돌리는데, 리스트나 트리 객체의 항목 개수는 새로운 항목의 추가나 기존 항목이 삭제될 때마다 증가하거나 감소하게 구현되어 있다.
    이처럼 리스트나 트리 객체에 변경이 생기더라도 정상적으로 항목들을 접근해서 사용할 있게 설계된 Iterator Robust Iterator라고 한다.
    Iterator 패턴의 구현 고려해야 다른 사항은 생성된 Iterator 객체를 소멸하는 문제다. 특히 [그림 20-3] 같이 Factory Method 패턴을 적용한 Polymorphic Iterator 경우에는 Iterator 객체가 CreateIterator() 멤버 함수에 의해 Heap 영역에 동적으로 생성된다. 따라서 Client 일일이 생성된 Iterator 객체를 소멸시키는 책임을 져야 하는 불편함이 있다. 이런 불편함을 없애기 위한 방법으로는 [소스 20-3] 같이 Proxy 패턴을 활용해서 사용이 끝난 Iterator 객체를 자동으로 소멸되게 만들어 주면 된다.
    [소스 20-3] Proxy 패턴을 활용해서 사용이 끝난 Iterator 객체는 자동으로 소멸되게 정의한 클래스
    class IteratorPtr
    {
    public:
    IteratorPtr(Iterator* pIter)
    : pIter_(pIter)
    {}
    ~IteratorPtr() { delete pIter_; }
     
    Iterator* operation->() { return pIter_; }
    Iterator& operation* () { return *pIter_; }
     
    protected:
    IteratorPtr(const IteratorPtr& rhs);
    IteratorPtr& operation = (const IteratorPtr& rhs);
     
    private:
    Iterator* pIter_;
    };
    밖에 Iterator 패턴의 구현과 관련해서 고려해볼만한 것은 Iterator 객체가 참조하는 자료구조가 Composite 패턴에 의해 생성된 객체일 어떤 식으로 항목을 따라가게 하는 것이 바람직할 것인가 하는 점이다.
    Composite 패턴을 적용해서 생성된 객체는 항목들이 트리 형태를 구성하게 된다. 이처럼 트리 구조를 순차적으로 접근하기 위해서는 [소스 20-2] TreeList 클래스에서처럼 트리 상의 항목들을 접근하기 위한 순서가 결정되어 있어야 한다. 또한 트리 상에서 항목들을 찾아 다니기 위해서는 항목들이 부모 항목, 자식 항목, 형제 항목들에 대한 정보를 가지고 있어야 하고, 이들을 이용해서 트릴르 순회하기 위한 별도의 알고리즘이 필요하다.
    만약 이런 알고리즘이 없이 Iterator 객체만으로 트리 상의 항목들을 따라가려면 External Iterator 사용하는 것보다 Internal Iterator 사용하는 것이 다소 편리할 있다. 왜냐하면 External Iterator 사용할 경우에는 트리 상에서 가지(Branch) 있을 때마다 Client 새로운 Iterator 객체를 생성해서 해당 가지 아래의 하위 트리를 순차적으로 접근하게 해야 하는데 비해 Internal Iterator 자체적으로 회귀 호출을 통해 가지들을 따라 내려가면서 트리 상의 항목 순회가 가능하기 때문이다. External Iterator Client 일일이 트리를 따라가는 과정을 처리해야 하지만, Internal Iterator 경우에는 그럴 필요가 없다는 것이다.
    • ITERATOR 패턴 정리
    Iterator 패턴이 유용한 경우
    • 여러 항목이 모인 Aggregate 클래스 객체에 대해 내부 구조를 신경쓰지 않고 항목을 접근하고자
    • 동일한 Aggregate 클래스 객체에 대해 다양한 형태의 항목 접근을 지원하고자
    • 서로 다른 구조를 가진 Aggregate 클래스 객체에 대해 항목을 접근하기 위한 인터페이스를 동일하게 정의하고자 , 이를 특별히 Polymorphic Iteration 지원한다고 얘기하기도 한다.
    Iterator패턴의 장단점과 패턴들과의 관계
    • Iterator 패턴은 Aggregate 객체 내의 항목을 다양한 형태로 접근하는 것이 쉽게 가능하도록 해준다. 파스 트리와 같이 복잡한 Aggregate 객체에 대해 코드 생성, 의미 검증 등을 하고자 경우, 파스 트리를 여러 가지 다양한 형태로 접근할 필요가 있다. 이때 Iterator 패턴을 사용하면 파스 트리 내의 항목을 접근하는 방법에 따라 Iterator 하위 클래스를 정의함으로써 쉽게 다양한 형태의 접근이 가능하게 된다.
    • 리스트 내의 항목들을 저장, 관리하는 Aggregate 클래스와 저장된 항목을 다양한 형태로 접근하기 위한 Iterator 클래스를 분리시켜둠으로써 Aggregate 클래스의 인터페이스가 무척 간단해질 있다.
    • 동일한 Aggregate 객체에 대해 여러 개의 Iterator 클래스 객체가 생성, 사용될 있다. 이것은 Iterator 객체마다 Aggregate 객체 내의 현재 위치 정보를 따로 관리하기 때문에 가능한 것이다.
    • Composite 패턴은 Aggregate 객체 내의 항목 구성 사용될 있다.
    • Factory Method 패턴은 Polymorphic Iterator 생성하고자 경우 사용될 있다.
    • Memento 패턴은 Iterator 객체 내부에 현재 항목의 위치 정보 등을 저장, 관리할 유용하게 사용할 있다.
반응형