11. Iterator Pattern ( 이터레이터 / 반복자 패턴 C# )

|

[읽기전에]

  UML 다이어그램 : UML클래스 다이어그램 기본상식 http://hongjinhyeon.tistory.com/25 

  포스팅되는 디자인 패턴의 예는 스타크래프트를 기본으로 하였습니다 : 디자인 패턴을 시작하며 http://hongjinhyeon.tistory.com/24



<기본 정의 >


1. Iterator Pattern 정의

  

  -컬렉션 구현 방법을 노출시키지 않으면서 그 집합체 안에 들어잇는 모든 항목에 접근할 수 있게 해주는 방법을 제공한다.

  (Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.)



2. UML Diagram





3. 사용용도 및 장점


  -반복자 패턴을 이용하면 집합체 내에서 어떤 식으로 일이 처리되는지 대해서 전혀 모르는 상태에서 그 안에

   있는 모든 항목들에 대해서 반복작업을 수행할 수 있다. 

 

 -클래스가 데이터를 저장함에 있어서 배열,ArrayList,해쉬테이블 등등의 데이터 구조를 사용한다 할지라도 이터레이터

  인터페이스를 구현하게 함으로써 외부에서는 동일한 인터페이스로 각각의 항목에 대해서 순차적으로 접근이 가능하다.




4. 소스


using System;
using System.Collections;
 
namespace DoFactory.GangOfFour.Iterator.Structural
{
    /// <summary>
    /// MainApp startup class for Structural
    /// Iterator Design Pattern.
    /// </summary>
    class MainApp
    {
        /// <summary>
        /// Entry point into console application.
        /// </summary>
        static void Main()
        {
            ConcreteAggregate a = new ConcreteAggregate();
            a[0] = "Item A";
            a[1] = "Item B";
            a[2] = "Item C";
            a[3] = "Item D";
 
            // Create Iterator and provide aggregate
            ConcreteIterator i = new ConcreteIterator(a);
 
            Console.WriteLine("Iterating over collection:");
 
            object item = i.First();
            while (item != null)
            {
                Console.WriteLine(item);
                item = i.Next();
            }
 
            // Wait for user
            Console.ReadKey();
        }
    }
 
    /// <summary>
    /// The 'Aggregate' abstract class
    /// </summary>
    abstract class Aggregate
    {
        public abstract Iterator CreateIterator();
    }
 
    /// <summary>
    /// The 'ConcreteAggregate' class
    /// </summary>
    class ConcreteAggregate : Aggregate
    {
        private ArrayList _items = new ArrayList();
 
        public override Iterator CreateIterator()
        {
            return new ConcreteIterator(this);
        }
 
        // Gets item count
        public int Count
        {
            get { return _items.Count; }
        }
 
        // Indexer
        public object this[int index]
        {
            get { return _items[index]; }
            set { _items.Insert(index, value); }
        }
    }
 
    /// <summary>
    /// The 'Iterator' abstract class
    /// </summary>
    abstract class Iterator
    {
        public abstract object First();
        public abstract object Next();
        public abstract bool IsDone();
        public abstract object CurrentItem();
    }
 
    /// <summary>
    /// The 'ConcreteIterator' class
    /// </summary>
    class ConcreteIterator : Iterator
    {
        private ConcreteAggregate _aggregate;
        private int _current = 0;
 
        // Constructor
        public ConcreteIterator(ConcreteAggregate aggregate)
        {
            this._aggregate = aggregate;
        }
 
        // Gets first iteration item
        public override object First()
        {
            return _aggregate[0];
        }
 
        // Gets next iteration item
        public override object Next()
        {
            object ret = null;
            if (_current < _aggregate.Count - 1)
            {
                ret = _aggregate[++_current];
            }
 
            return ret;
        }
 
        // Gets current iteration item
        public override object CurrentItem()
        {
            return _aggregate[_current];
        }
 
        // Gets whether iterations are complete
        public override bool IsDone()
        {
            return _current >= _aggregate.Count;
        }
    }
}


 -Iterator는 Aggregate 객체를 참조하여 해당 클래스의 ArrayList를 외부에서 순차적으로 확인이 가능합니다.


 -순차적으로 이동 또는 스텝을 정해서 짝수에 해당하는 아이템만 리턴이 가능하게도 설정이 가능하며, 특정 아이템의 삭제도

  가능하게 변경이 가능합니다. ( 사용용도에 따라서 확장이 가능 )



5. 실행결과


Iterating over collection:

Item A

Item B

Item C

Item D




< 실제 적용 >



1. UML Diagram






2. 사용용도 및 장점


 -스타크래프트에서는 자신들의 유닛을 최대 10마리씩 10개의 번호에(1~10) 에 미리 지정할 수 있습니다. ( HotKey 기능:부대지정 ) 해당 번호를 누르면

   저장된 유닛들이 모두 선택이 됩니다. 옵저버 유닛은 적의 클로킹된 유닛( 보이지 않는 유닛 ) 을 보여주게 하는 기능을 합니다. 옵져버의 영역

   에 속한 적유닛들에 대한 리스트가 존재하게 됩니다.  

   

   플레이어의 현황을 관리하는 클래스에서는 위의 두 상황에서 1번 Hotkey에 저장된 유닛을 호출할 경우도 있으며, 옵져버가 밝히는 적 유닛의 

   리스트도 호출할 경우가 존재합니다. 이런경우에 이터레이터 패턴을 사용하게 되면,  플레이어의 현황을 관리하는 클래스에서는 HotKey나 Observer

   내부의 상황을 몰라도 UnitIterator의 인터페이스로 모두 접근이 가능하게 됩니다.


 -또한 다른 유닛들을 소유하는 클래스를 설계 할때 확장에도 용이합니다. UnitContainer를 상속받는 클래스를 추가후에, 해당 데이터 구조에 해당하는

   UnitIterator를 구현하면 플레이어의 현황을 관리하는 클래스에서는 이전 소스와 동일하게 사용이 가능하게 됩니다.



3. 소스


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Iterator
{
    class Program
    {
        static void Main(string[] args)
        {
            HotKey HotKey1 = new HotKey();
            HotKey1.addUnit(new Unit("마린1"));
            HotKey1.addUnit(new Unit("마린2"));
            HotKey1.addUnit(new Unit("마린3"));
            HotKey1.addUnit(new Unit("메딕1"));
            HotKey1.addUnit(new Unit("메딕2"));
            HotKey1.addUnit(new Unit("파이어뱃1"));

            UnitIterator Iterator = HotKey1.CreateIterator();

            while (Iterator.hasNext())
            {
                Console.WriteLine("핫키에 등록된 유닛:{0}", Iterator.Next().m_strName);
            }

            Console.WriteLine(Environment.NewLine);

            Observer Observer1 = new Observer();
            Observer1.addUnit(new Unit("적 다크템플러1"));
            Observer1.addUnit(new Unit("적 다크템플러2"));
            Observer1.addUnit(new Unit("적 다크템플러3"));
            Observer1.addUnit(new Unit("적 레이쓰1"));
            Observer1.addUnit(new Unit("적 레이쓰2"));
            Observer1.addUnit(new Unit("적 고스트1"));

            Iterator = Observer1.CreateIterator();

            while (Iterator.hasNext())
            {
                Console.WriteLine("옵저버1이 밝혀진 클락킹된 유닛:{0}", Iterator.Next().m_strName);
            }

            Console.ReadKey();
        }

        /// <summary>
        /// 유닛 클래스 생성
        /// </summary>
        class Unit
        {
            public string m_strName { get; set; }

            public Unit(string _strName)
            {
                m_strName = _strName;
            }
        }

        /// <summary>
        /// 반복자 추상 클래스 생성
        /// </summary>
        abstract class UnitIterator
        {
            public  abstract Unit Next();
            public  abstract bool hasNext();
        }

        /// <summary>
        /// 핫키를 관리하는 반복자 클래스 생성
        /// </summary>
        class HotkeyIterator : UnitIterator
        {

            private HotKey m_HotKey;
            private int m_intCurrent;

            public HotkeyIterator(HotKey _Hotkey)
            {
                m_HotKey = _Hotkey;
                m_intCurrent = 0;
            }

            public override Unit Next()
            {
               return m_HotKey[m_intCurrent++];
            }

            public override bool  hasNext()
            {
                return m_intCurrent < m_HotKey.Count();
            }

        }

        class ObserverIterator : UnitIterator
        {
            private Observer m_Observer;
            private int m_intCurrent;

            public ObserverIterator(Observer _Observer)
            {
                this.m_Observer = _Observer;
                m_intCurrent = 0;
            }

            public override Unit Next()
            {
                return m_Observer[m_intCurrent++];
            }

            public override bool hasNext()
            {
                return m_intCurrent < m_Observer.Count();
            }

        }

        /// <summary>
        /// 유닛을 관리하는 추상 클래스 생성
        /// </summary>
        abstract class UnitContainer
        {
            public  abstract UnitIterator CreateIterator();
        }

        /// <summary>
        /// 유닛을 소유하고 있는 핫키 클래스 생성
        /// </summary>
        class HotKey : UnitContainer
        {
            private Unit[] m_HotKeyUnit = new Unit[10];
            private int m_Count = 0;

            public override UnitIterator  CreateIterator()
            {
                return new HotkeyIterator(this);
            }

            /// <summary>
            /// 유닛 추가 (배열)
            /// </summary>
            /// <param name="_Unit"></param>
            public void addUnit(Unit _Unit)
            {
                if (m_Count<10)
                {
                    this.m_HotKeyUnit[m_Count] = _Unit;
                    m_Count++;
                }

            }

            public int Count()
            {
                return m_Count;
            }

            public Unit this[int _index]
            {
                get { return m_HotKeyUnit[_index]; }
            }

        }

        /// <summary>
        /// 클라킹된 유닛을 소유하는 옵져버 클래스 생성
        /// </summary>
        class Observer : UnitContainer
        {
            private List<Unit> m_ObserverUnit = new List<Unit>();

            public override UnitIterator  CreateIterator()
            {
                return new ObserverIterator(this);
            }

            public void addUnit(Unit _Unit)
            {
                this.m_ObserverUnit.Add(_Unit);
            }

            public int Count()
            {
                return m_ObserverUnit.Count;
            }

            public Unit this[int _index]
            {
                get { return m_ObserverUnit[_index]; }
            }
        }

    }
}

 -HotKey와 Observer에서 Unit을 저장함에 있어서 각각 배열과 리스트를 사용하였습니다. HotKey는 최대 10개의 유닛만 사용이 가능하므로

  10개로 멤버변수를 사용하며, Observer는 옵져버가 밝히는 영역의 모든 클라킹된 유닛을 보여주므로 리스트를 이용합니다.


 -두개의 클래스에서 각각 다른 배열과 리스트를 사용한다 할지라도 Iterator에서는 동일하게 접근하여 처리가 가능합니다.



4. 실행결과






5. 피드백


 -처음에는 별로 안중요하다고 생각이 들었지만 보면 볼수록 좋은 패턴같습니다.


 -서로 다른 사람이 여러개의 클래스를 개발한다고 할때, 이터레이터의 인터페이스를 준수해서 구현을 하게되면 사용하는 곳에서는

   항상 동일한 코드로 접근이 가능하니 편리한거 같습니다.

 


[참조]

 -http://www.dofactory.com

 -Head First Design Patterns

And