Итератор (Iterator)

Паттерн Итератор (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# и .NET

Формальное определение паттерна на 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, который использует итератор для обхода объекта библиотеки.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *