Паттерн Итератор (Iterator) предоставляет абстрактный интерфейс для последовательного доступа ко всем элементам составного объекта без раскрытия его внутренней структуры.
Наверное, всем программистам, работающим с языком C#, приходилось иметь дело с циклом foreach
, который перебирает объекты в
массиве или коллекции. При этом встроенных классов коллекций существует множество, и каждая из них отличается по своей структуре и поведению.
Ключевым моментом, который позволяет осуществить перебор коллекций с помощью foreach
, является применения этими классами
коллекций паттерна итератор, или проще говоря пары интерфейсов IEnumerable / IEnumerator
. Интерфейс IEnumerator
определяет функционал для перебора внутренних объектов в контейнере:
public interface IEnumerator { bool MoveNext(); // перемещение на одну позицию вперед в контейнере элементов object Current {get;} // текущий элемент в контейнере void Reset(); // перемещение в начало контейнера }
А интерфейс IEnumerable использует IEnumerator для получения итератора для конкретного типа объекта:
public interface IEnumerable { IEnumerator GetEnumerator(); }
Используя данные интерфейсы, мы можем свести к одному шаблону - с помощью цикла foreach - любые составные объекты.
Когда необходимо осуществить обход объекта без раскрытия его внутренней структуры
Когда имеется набор составных объектов, и надо обеспечить единый интерфейс для их перебора
Когда необходимо предоставить несколько альтернативных вариантов перебора одного и того же объекта
С помощью схем UML итераторы можно описать так:
Формальное определение паттерна на C# может выглядеть следующим образом:
class Client { public void Main() { Aggregate a = new ConcreteAggregate(); Iterator i = a.CreateIterator(); object item = i.First(); while (!i.IsDone()) { item = i.Next(); } } } abstract class Aggregate { public abstract Iterator CreateIterator(); public abstract int Count { get; protected set; } public abstract object this[int index] { get; set; } } class ConcreteAggregate : Aggregate { private readonly ArrayList _items = new ArrayList(); public override Iterator CreateIterator() { return new ConcreteIterator(this); } public override int Count { get { return _items.Count; } protected set { } } public override object this[int index] { get { return _items[index]; } set { _items.Insert(index, value); } } } abstract class Iterator { public abstract object First(); public abstract object Next(); public abstract bool IsDone(); public abstract object CurrentItem(); } class ConcreteIterator : Iterator { private readonly Aggregate _aggregate; private int _current; public ConcreteIterator(Aggregate aggregate) { this._aggregate = aggregate; } public override object First() { return _aggregate[0]; } public override object Next() { object ret = null; _current++; if (_current < _aggregate.Count) { ret = _aggregate[_current]; } return ret; } public override object CurrentItem() { return _aggregate[_current]; } public override bool IsDone() { return _current >= _aggregate.Count; } }
Iterator: определяет интерфейс для обхода составных объектов
Aggregate: определяет интерфейс для создания объекта-итератора
ConcreteIterator: конкретная реализация итератора для обхода объекта Aggregate. Для фиксации индекса
текущего перебираемого элемента использует целочисленную переменную _current
ConcreteAggregate: конкретная реализация Aggregate. Хранит элементы, которые надо будет перебирать
Client: использует объект Aggregate и итератор для его обхода
Теперь рассмотрим конкретный пример. Допустим, у нас есть классы книги и библиотеки:
class Book { public string Name { get; set; } } class Library { private Book[] books; }
И, допустим, у нас есть класс читателя, который хочет получить информацию о книгах, которые находятся в библиотеке. И для этого надо осуществить перебор объектов с помощью итератора:
class Program { static void Main(string[] args) { Library library = new Library(); Reader reader = new Reader(); reader.SeeBooks(library); Console.Read(); } } class Reader { public void SeeBooks(Library library) { IBookIterator iterator = library.CreateNumerator(); while(iterator.HasNext()) { Book book = iterator.Next(); Console.WriteLine(book.Name); } } } interface IBookIterator { bool HasNext(); Book Next(); } interface IBookNumerable { IBookIterator CreateNumerator(); int Count { get; } Book this[int index] { get;} } class Book { public string Name { get; set; } } class Library : IBookNumerable { private Book[] books; public Library() { books = new Book[] { new Book{Name="Война и мир"}, new Book {Name="Отцы и дети"}, new Book {Name="Вишневый сад"} }; } public int Count { get { return books.Length; } } public Book this[int index] { get { return books[index]; } } public IBookIterator CreateNumerator() { return new LibraryNumerator(this); } } class LibraryNumerator : IBookIterator { IBookNumerable aggregate; int index=0; public LibraryNumerator(IBookNumerable a) { aggregate = a; } public bool HasNext() { return index<aggregate.Count; } public Book Next() { return aggregate[index++]; } }
Интерфейс IBookIterator представляет итератор наподобие интерфейса IEnumerator. Роль интерфейса составного агрегата представляет тип IBookNumerable. Клиентом здесь является класс Reader, который использует итератор для обхода объекта библиотеки.