“Патерн Вівторка” #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

Add a Comment

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *