→ Пошук по сайту       Увійти / Зареєструватися
Знання Патерни Патерни поведінки — Behavioral patterns

16. Ітератор — Iterator

16.	Ітератор — Iterator

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

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

Таким чином в застосуванні до нашої проблеми, ми хочемо щоб SoldiersIterator «пробігся» по всіх солдатах. Код нище показує використання Ітератора.

Уривок коду 16.1. Використання патерну — ітерування по армії

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

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

Уривок коду 16.2. Структура армії

 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) і основна різниця полягає в тому, що він має вищий початковий рівень здоров’я.

Уривок коду 16.3. Солдат та солдат-герой

 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);
    }
}
class Hero : Soldier
{
    private const int HeroHealthPoints = 500;
    protected override int MaxHealthPoints { get { return HeroHealthPoints; } }

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

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

Уривок коду 16.4. Ітератор і складна логіка ітерування (яку не раджу читати)

 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;
        if (_currentGroup < _army.ArmyGroups.Count)
        {
            // В кожній групі ітеруємо по кожному солдату
            if (_currentGroupSoldier < _army.ArmyGroups[_currentGroup].Soldiers.Count)
            {
                nextSoldier =                 
                       _army.ArmyGroups[_currentGroup].Soldiers[_currentGroupSoldier];
                _currentGroupSoldier++;
            }
            else
            {
                _currentGroup++;
                _currentGroupSoldier = 0;
                return Next();
            }
        }
        // Герой останнім покидає поле бою
        else if (!_heroIsIterated)
        {
            _heroIsIterated = true;
            return _army.ArmyHero;
        }
        else
        {
            // Викидуємо виняток
            throw new Exception("End of colletion");
        }
        return nextSoldier;
    }
}

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

Уривок коду 16.5. Просте створення ітератора

 var iterator = new SoldiersIterator(earthArmy);

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

Уривок коду 16.6. Класичне створення ітератора

 AbstractIterator iterator = AbstractArmy.GetSoldiersIterator();

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

 var list = new List<int>();
// GetEnumerator це метод інтерфейсу IEnumerable (агрегат)
var enumerator = list.GetEnumerator();
// MoveNext метод інтерфейсу IEnumerator і буде методом ітератора
enumerator.MoveNext();

По матеріалам книги Андрія Будая "Дизайн патерни – просто, як двері". Матеріал розміщується за домовленістю з автором.
Робота представлена за умовами ліцензії Creative Commons Attribution-NonCommercial 3.0 Unported License.

загрузка...
Сторінки, близькі за змістом