piątek, 14 maja 2010

Iterator Pattern

Iterator jest jednym z najczęściej wykorzystywanych szablonów w C#. Nawet nie zdając sobie z tego sprawy - korzystasz z niego na co dzień. Pozwala on na sekwencyjny dostęp do danych zawartych w kontenerze (lista, stos, kolejka itp.). Dzięki niemu zachowujemy enkapsulację oraz możemy w łatwy sposób zaimplementować dostęp do elementów od początku/końca struktury danych, przeskok o żądaną ilość obiektów itp.

Ogólny schemat Iteratora przedstawia poniższy schemat UML:


A więc zaczynając od lewej strony Aggregate tworzy interfejs tworzenia iteratora. ConcreteAggregate implementuje metodę tworzącą żądany iterator. Iterator tworzy interfejs umożliwiający przeglądanie elementów kontenera. ConcreteIterator - implementuje metody interfejsu oraz przechowuje informacje o aktualnej pozycji w przeglądanym kontenerze.

A więc przejdźmy do jakiegoś konkretnego przykładu. Wyobraźmy sobie że tworzymy jakiś kontener do przechowywania danych (SimpleList). Chcemy teraz w prosty sposób przeglądać nasze elementy. Dzięki implementacji iteratora możemy w łatwy sposób osiągnąć zamierzony cel:

    public class ListNode
    {
        private int _data;

        public int Data
        {
            get { return _data; }
            set { _data = value; }
        }

        private ListNode _next;

        public ListNode Next
        {
            get { return _next; }
            set { _next = value; }
        }

        public ListNode(int data, ListNode next)
        {
            _data = data;
            _next = next;
        }

        public ListNode(int data)
            : this(data, null)
        {
        }

        public ListNode()
            : this(0, null)
        {
        }
    }

    public class SimpleList
    {
        private ListNode _head;

        public SimpleList()
        {
            _head = null;
        }

        public IIterator CreateIterator()
        {
            return new SimpleListIterator(_head);
        }

        public void AddToFront(int date)
        {
            ListNode node = new ListNode(date);
            if (_head == null)
            {
                _head = node;
            }
            else
            {
                node.Next = _head;
                _head = node;
            }
        }

        public void AddToEnd(int data)
        {
            ListNode node = new ListNode(data);
            ListNode tmp = _head;
            if (_head == null)
            {
                _head = node;
            }
            else
            {
                while (tmp.Next != null)
                {
                    tmp = tmp.Next;
                }
                tmp.Next = node;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            SimpleList simpleList = new SimpleList();
            simpleList.AddToFront(5);
            simpleList.AddToFront(6);
            simpleList.AddToFront(7);
            simpleList.AddToEnd(20);
            simpleList.AddToFront(30);
            simpleList.AddToEnd(25);

            IIterator simpleListIterator = simpleList.CreateIterator();
            while (simpleListIterator.HasNext())
            {
                ListNode ln = (ListNode)simpleListIterator.Next();
                Console.WriteLine(ln.Data);
            }
            Console.ReadLine();
        }
    }

    public class SimpleListIterator : IIterator
    {
        ListNode listNode;
        ListNode position;

        public SimpleListIterator(ListNode head)
        {
            listNode = head;
            position = head;
        }

        public object Next()
        {
            ListNode tmp = new ListNode();
            tmp.Data = position.Data;
            tmp.Next = position.Next;

            position = position.Next;
            return tmp;
        }

        public bool HasNext()
        {
            if (position != null)
            {
                return true;
            }
            return false;
        }

        public object First()
        {
            return listNode;
        }
    }

    public interface IIterator
    {
        object Next();

        bool HasNext();

        object First();
    }


C# domyślnie ma wbudowany iterator w kolekcjach jak i zwykłych tablicach. C# odpowiednik iteratora nazywany jest enumeratorem. Wszystkie klasy implementujące interfejs IEnumerable pozwalają na korzystanie z przechodzenia w sposób jawny i niejawny kolekcji:

        static void Main(string[] args)
        {
            List<int> list = new List<int>();
            Random rand = new Random();
            for (int i = 0; i < 5; i++)
            {
                list.Add(rand.Next(100));
            }

            //jawny sposób
            List<int>.Enumerator iterator = list.GetEnumerator();
            while (iterator.MoveNext())
            {
                Console.WriteLine(iterator.Current);
            }

            Console.WriteLine();

            //niejawny
            foreach (var item in list)
            {
                Console.WriteLine(item);
            }
        }

Ze sposobu niejawnego korzystaliśmy z pewnością nieraz. Spróbujmy więc zaimplementować enumerator i generyczność do klasy SimpleList:

    public class SimpleList<T> : IEnumerable<T>

    {

        private ListNode<T> _head;

 

        public SimpleList()

        {

            _head = null;

        }

 

        public void AddToFront(T date)

        {

            ListNode<T> node = new ListNode<T>(date);

            if (_head == null)

            {

                _head = node;

            }

            else

            {

                node.Next = _head;

                _head = node;

            }

        }

 

        public void AddToEnd(T data)

        {

            ListNode<T> node = new ListNode<T>(data);

            ListNode<T> tmp = _head;

            if (_head == null)

            {

                _head = node;

            }

            else

            {

                while (tmp.Next != null)

                {

                    tmp = tmp.Next;

                }

                tmp.Next = node;

            }

        }

 

        public IEnumerator<T> GetEnumerator()

        {

            ListNode<T> tmp = _head;

            while (tmp != null)

            {

                T data = tmp.Data;

                tmp = tmp.Next;

                yield return data;

            }

        }

 

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()

        {

            return GetEnumerator();

        }

    }

 

    class Program

    {

        static void Main(string[] args)

        {

            SimpleList<int> simpleList = new SimpleList<int>();

            simpleList.AddToFront(5);

            simpleList.AddToFront(6);

            simpleList.AddToFront(7);

            simpleList.AddToEnd(20);

            simpleList.AddToFront(30);

            simpleList.AddToEnd(25);

 

            foreach (var item in simpleList)

            {

                Console.WriteLine(item);

            }

        }

    }


Jak wiadać ilość kodu się zmniejszyła a żądana funkcjonalność została.

Iterator to ważny wzorzec projektowy i warto go znać i stosować.

Brak komentarzy:

Prześlij komentarz