12 жовтня 2010 р.

"Патерн Вівторка" #10: Ітератор (Iterator)

Уявіть, що ви розробник статегічної воєнної гри. Армія має складну структуру: вона складається із героя і трьох груп. Коли Король видає указ і ресурси щоб полікувати всіх воїнів (Герой також є воїном) Ви хочете проітерувати по всіх солдатах і викликати метод treat() на кожному інстансі. Як це можна зробити легко і без вникання в структуру Арімії?

ITERATOR

Ітератор це патерн який дозволяє доступатися почергово до елементів будь-якої колекції без вникання в суть її імплементації.

Таким чином в застосуванні до нашої проблеми:

Ми не хочемо перейматися структурою Армії - ми хочемо щоб SoldiersIterator пробігся по всіх солдатах.






Використання

Код нище показує використання ітератора. Як бачимо ми просто отримали інстанс ітератора SoldiersIterator. І простим циклом проходимося по всіх солдатах армії. Це дуже легко, що і є основним завданням ітератора.

            var iterator = new SoldiersIterator(earthArmy);
            while (iterator.HasNext())
            {
                var currSoldier = iterator.Next();
                currSoldier.Treat();
            }

Структура Армії
   
Армія складається із одного героя і може містити багато Груп, кожна із яких може містити багато солдат. Отже, як ми бачимо, сктуктура армії складна і деревовидна. Код нижче показує створення Армії:

            var andriybuday = new Hero("Andriy Buday");
            var earthArmy = new Army(andriybuday);

            var groupA = new Group();
            for (int i = 1; i < 4; ++i)
                groupA.AddNewSoldier(new Soldier("Alpha:" + i));

            var groupB = new Group();
            for (int i = 1; i < 3; ++i)
                groupB.AddNewSoldier(new Soldier("Beta:" + i));

            var groupC = new Group();
            for (int i = 1; i < 2; ++i)
                groupC.AddNewSoldier(new Soldier("Gamma:" + i));
            
            earthArmy.AddArmyGroup(groupB);
            earthArmy.AddArmyGroup(groupA);
            earthArmy.AddArmyGroup(groupC);

Герой (Hero) це клас унаслідуваний від солдата(Soldier) і основна різниця така, що він має вищий рівень здоров"я.

    public class Soldier
    {
        public String Name;
        public int Health;
        private const int SoldierHealthPoints = 100;
        protected virtual int MaxHealthPoints { get { return SoldierHealthPoints; } }

        public Soldier(String name)
        {
            Name = name;
        }

        public void Treat()
        {
            Health = MaxHealthPoints;
            Console.WriteLine(Name);
        }
    }

    public class Hero : Soldier
    {
        private const int HeroHealthPoints = 500;
        protected override int MaxHealthPoints { get { return HeroHealthPoints; } }

        public Hero(String name)
            : base(name)
        {
        }
    }
SoldiersIterator

То ж, якщо ми можемо рухатися по складній колекції так легко, де є вся складність? Звісно, вона прихована інкапсульована в конкретному класі ітератора.

    public class SoldiersIterator
    {
        private readonly Army _army;
        private bool _heroIsIterated;
        private int _currentGroup;
        private int _currentGroupSoldier;

        public SoldiersIterator(Army army)
        {
            _army = army;
            _heroIsIterated = false;
            _currentGroup = 0;
            _currentGroupSoldier = 0;
        }

        public bool HasNext()
        {
            if (!_heroIsIterated) return true;
            if (_currentGroup < _army.ArmyGroups.Count) return true;
            if (_currentGroup == _army.ArmyGroups.Count - 1)
                if (_currentGroupSoldier < _army.ArmyGroups[_currentGroup].Soldiers.Count) return true;

            return false;
        }

        public Soldier Next()
        {
            Soldier nextSoldier;
            // we still not iterated all soldiers in current group
            if (_currentGroup < _army.ArmyGroups.Count)
            {
                if (_currentGroupSoldier < _army.ArmyGroups[_currentGroup].Soldiers.Count)
                {
                    nextSoldier = _army.ArmyGroups[_currentGroup].Soldiers[_currentGroupSoldier];
                    _currentGroupSoldier++;
                }
                // moving to next group
                else
                {
                    _currentGroup++;
                    _currentGroupSoldier = 0;
                    return Next();
                }
            }
            // hero is the last who left the battlefield
            else if (!_heroIsIterated)
            {
                _heroIsIterated = true;
                return _army.ArmyHero;
            }
            else
            {
                // THROW EXCEPTION HERE
                throw new Exception("End of colletion");
                //or set all counters to 0 and start again, but not recommended
            }
            return nextSoldier;
        }
    } 
Чому мій приклад не є стандартмим і класичним?

Тому що я собі поставив за завдання підкреслити головне завдання яке вирішує цей паттерн і зробити це таким чином, що можна буде все легко зрозуміти. Ще однією причиною є те, що ви можете прочитати тонни стандартних пояснень цього патерну. Головною ж різницею між моїм поясненням і іншими поясненнями є те, що стандартні є більш абстраговані. Наприклад, я створював потрібний нам ітератор таким чином:

var iterator = new SoldiersIterator(earthArmy);

Але зазвичай створення ітератора також інкапсулюється під методом Агрегата (як GetEnumerator в .NET). Мій код міг б виглядати так:

AbstractIterator iterator = AbstractArmy.GetSoldiersIterator();

В світі .NET ми маємо інтерфейси IEnumerable і IEnumerator, які нам допомагають у використанні цього патерну.

var list = new List();
//GetEnumerator is method of IEnumerator (Aggregate)
var enumerator = list.GetEnumerator();
//MoveNext method of IEnumerable (Iterator)
enumerator.MoveNext();

Моя табличка Патернів
Developer's RoadMap To Success

Немає коментарів:

Дописати коментар