Patterns. Паттерн Iterator. Реализация на C#

Паттерн Iterator. Реализация на C#

Перед изучением данной темы рекомендуется ознакомиться с темой:


Содержание


Поиск на других ресурсах:




1. Структура паттерна Iterator в кодах C#. Рисунок

Информацию о паттерне Iterator и способах его реализации можно получить здесь.

На рисунке 1 изображена структура паттерна Iterator с фрагментами кодов на языке C#. Данная структура может быть использована для любого количества контейнеров (полиморфный контейнер) и любого количества итераторов (полиморфный итератор).

Для того, чтобы добавить свой специфический контейнер, нужно в классе реализовать интерфейс IAggregate. В этом интерфейсе объявляется единственный метод CreateIterator(). Этот метод возвращает итератор для контейнера.

Паттерны. Структура паттерна Iterator. Коды на C#

Рисунок 1. Структура паттерна Iterator

 

2. Программирование составляющих, определяющих структуру паттерна Iterator

Для реализации паттерна Iterator нужно разработать следующие интерфейсы и классы:

  • интерфейс IAggregate, содержащий объявления метода CreateIterator();
  • интерфейс IIterator, содержащий базовые методы получения итератора и движения по контейнеру;
  • один или несколько конкретных итераторов. Здесь могут быть объявлены итераторы, осуществляющих различные стратегии обхода. Все итераторы реализуют интерфейс IIterator;
  • один или несколько классов, реализующих интерфейс IAggregate.

 

2.1. Интерфейс контейнера IAggregate<T>

Прежде всего объявляется интерфейс контейнера IAggregate<T>, содержащий объявления следующих методов:

  • CreateIterator() — возвращает итератор для контейнера;
  • Count() — возвращает количество элементов контейнера;
  • GetItem() — возвращает элемент контейнера по его индексу.

Согласно синтаксису C# все эти методы должны быть обязательно реализованы в классах, реализующих интерфейс IAggregate<T>.

После ввода интерфейса листинг программы следующий:

using System;

namespace ConsoleApp17
{
  // Реализация паттерна Iterator на C#.
  // Пример для консольного приложения
  // 1. Объявить интерфейс контейнера с поддержкой обобщенного типа T
  interface IAggregate<T>
  {
    // Вернуть итератор на контейнер
    IIterator<T> CreateIterator();

    // Количество элементов контейнера
    long Count();

    // Вернуть элемент по его индексу
    T GetItem(long index);
  }

  class Program
  {
    static void Main(string[] args)
    {
    }
  }
}

Интерфейс служит связующим звеном между подклассами, что его реализуют и интерфейсом IIterator<T>.

 

2.2. Интерфейс итератора IIterator<T>

Для обеспечения возможности реализации различных видов итераторов, нужно объявить интерфейс итератора IIterator<T>. Этот интерфейс будет содержать общие методы для всех классов итераторов, которые будут его реализовывать.

В нашем случае в интерфейс IIterator<T> вводятся следующие объявления методов:

  • First() — перейти на первый элемент контейнера;
  • Next() — переместить курсор на следующий элемент контейнера;
  • IsDone() — проверка, конец ли списка;
  • CurrentItem() — получить текущий элемент по курсору.

По желанию можно расширить список методов для унаследованных итераторов. Например, добавить метод Previous() для перехода к предыдущему элементу контейнера.

Сокращенный код программы после ввода интерфейса следующий.

using System;

namespace ConsoleApp17
{
  // Реализация паттерна Iterator на C#.
  // Пример для консольного приложения
  // 1. Объявить интерфейс контейнера с поддержкой обобщенного типа T
  interface IAggregate<T>
  {
    ...
  }

  // 2. Объявить интерфейс итератора для типа T
  interface IIterator<T>
  {
    // Переход на первый элемент контейнера
    void First();

    // Переход на следующий элемент контейнера
    void Next();

    // Получить текущий элемент
    T CurrentItem();

    // Проверка, указывает ли курсор на конец контейнера
    bool IsDone();
  }

  ...
}

 

2.3. Конкретный итератор ConcreteIterator<T>

На этом этапе создается один или несколько классов, реализующих интерфейс IIterator<T>. В нашем случае создается один класс с именем ConcreteIterator1<T>. Этот класс реализует прямой итератор.

В конкретном итераторе реализованы следующие программные элементы:

  • внутреннее поле aggregate — это ссылка на контейнер, для которого создается данный итератор;
  • внутреннее поле current — значение (позиция), которое указывает на один из элементов контейнера. Это курсор;
  • конструктор, который получает агрегатный объект в качестве параметра;
  • методы First(), Next(), IsDone() и CurrentItem(), которые переопределяют методы интерфейса IIterator<T>.

 

using System;

namespace ConsoleApp17
{
  // Реализация паттерна Iterator на C#.
  // Пример для консольного приложения

  // 1. Объявить интерфейс контейнера с поддержкой обобщенного типа T
  interface IAggregate<T>
  {
    ...
  }

  // 2. Объявить интерфейс итератора для типа T
  interface IIterator<T>
  {
    ...
  }

  // 3. Конкретный итератор
  class ConcreteIterator1<T> : IIterator<T>
  {
    // внутренние данные
    private IAggregate<T> aggregate; // Ссылка на агрегат
    private long current; // текущая позиция

    // Конструктор, получающий агрегированный объект
    public ConcreteIterator1(IAggregate<T> obj)
    {
      aggregate = obj;
      current = 0;
    }

    // Перевод курсора в начало списка
    virtual public void First()
    {
      current = 0;
    }

    // Перевод курсора на следующий элемент списка
    virtual public void Next()
    {
      current++;
    }

    // Проверка, конец ли списка
    virtual public bool IsDone()
    {
      return current >= aggregate.Count();
    }

    // Вернуть текущий элемент списка
    virtual public T CurrentItem()
    {
      if (!IsDone())
        return aggregate.GetItem(current);
      else
      {
        throw new NotImplementedException("Error");
      }
    }
  }

  ...
}

По вышеприведенному образцу кода можно добавлять другие виды итераторов с произвольными именами. Обязательным условием является реализация (наследование) интерфейса IIterator<T>.

Если нужно добавить собственный итератор то выполняются следующие шаги:

  • выбрать имя итератора и объявить класс этого итератора (например, ConcreteIterator2<T> или другое). Класс должен реализовывать (быть унаследован от) интерфейс IIterator<T>;
  • реализовать внутренние данные и один или несколько конструкторов;
  • реализовать методы First(), Next(), IsDone(), CurrentItem() объявленные в интерфейсе IIterator<T>;
  • по желанию объявить дополнительные методы.

Одной из разновидностей итератора является обратной итератор который просматривает контейнер с конца до начала. В нашем случае, если бы нужно было реализовать обратной итератор (просматривает элементы контейнера от конца до начала), то некоторые методы должны иметь иную трактовку:

  • метод First() реализовывал бы переход на последний элемент контейнера;
  • метод Next() обеспечивал бы перемещение курсора на предыдущий элемент контейнера.

 

2.4. Класс ConcreteAggregate<T>

Объявленный в п. 2.1 интерфейс IAggregate<T> является общим для всех классов, реализующих конкретный агрегат (контейнер). Благодаря полиморфизму в программе может быть реализовано любое количество конкретных агрегатов. Как известно, в каждом классе контейнера данные представлены в виде набора элементов (список, массив, дерево и т.д.).

Если создается класс конкретного контейнера (агрегата), то к нему предъявляется ряд требований:

  • класс обязательно должен реализовывать (наследовать) интерфейс IAggregate<T>;
  • класс должен содержать минимальный функционал для оперирования набором элементов;
  • класс должен реализовывать методы интерфейса IAggregate<T>. Обязательным методом является метод CreateIterator().

В нашем случае объявляется класс ConcreteAggregate1<T>, который реализует интерфейс IAggregate<T>. Класс содержит следующие основные составляющие:

  • внутреннее поле-массив array обобщенного типа T. Вместо массива может использоваться любая другая структура данных: список, дерево, множество и тому подобное;
  • два конструктора;
  • метод Append() — добавляет новый элемент в конец массива;
  • метод Remove() — удаляет элемент из массива по указанной позиции;
  • метод Print() — выводит массив.
  • методы, объявленные в интерфейсе IAggregate<T>: CreateIterator(), Count(), GetItem().

Сокращенный фрагмент программы с кодом класса ConcreteAggregate1<T> имеет вид.

 

using System;

namespace ConsoleApp17
{
  // Реализация паттерна Iterator на C#.
  // Пример для консольного приложения

  // 1. Объявить интерфейс контейнера с поддержкой обобщенного типа T
  interface IAggregate<T>
  {
    ...
  }

  // 2. Объявить интерфейс итератора для типа T
  interface IIterator<T>
  {
    ...
  }

  // 3. Конкретный итератор
  class ConcreteIterator1<T> : IIterator<T>
  {
    ...
  }

  // 4. Конкретный контейнер для элементов типа T
  class ConcreteAggregate1<T>:IAggregate<T>
  {
    // Внутренние поля
    private T[] array; // динамический массив элементов типа T

    // Конструкторы
    // Конструктор, получающий внешний массив
    public ConcreteAggregate1(T[] _array)
    {
      array = _array;
    }

    // Конструктор, создающий пустой массив
    public ConcreteAggregate1()
    {
      array = null;
    }

    // Метод, добавляющий элемент в конец контейнера
    public void Append(T item)
    {
      T[] array2 = array;
      array = new T[array2.Length + 1];
      array2.CopyTo(array, 0);
      array[array.Length - 1] = item;
    }

    // Удаляет элемент из контейнера в позиции index
    public void Remove(long index)
    {
      T[] array2 = array;
      array = new T[array2.Length - 1];

      for (long i = 0; i < index; i++)
        array[i] = array2[i];

      for (long i = index + 1; i < array2.Length; i++)
        array[i - 1] = array2[i];
    }

    // Вывести содержимое контейнера
    public void Print(string text)
    {
      Console.WriteLine(text + ":");
      for (int i = 0; i < array.Length; i++)
        Console.Write(array[i] + " ");
      Console.WriteLine();
    }

    // Реализация методов интерфейса IAggregate<T>
    // Вернуть итератор
    public IIterator<T> CreateIterator()
    {
      return new ConcreteIterator1<T>(this);
    }

    // Количество элементов
    public long Count()
    {
      return array.Length;
    }

    // Вернуть отдельный элемент
    public T GetItem(long index)
    {
      if ((index >= 0) && (index < array.Length))
        return array[index];
      else
        throw new NotImplementedException("Error. Bad index");
    }
  }
}

 

3. Код клиента. Демонстрация паттерна

Ниже приведены клиентский код, демонстрирующий получение итератора и его использование.

 

using System;

namespace ConsoleApp17
{
  // Реализация паттерна Iterator на C#.
  // Пример для консольного приложения

  // 1. Объявить интерфейс контейнера с поддержкой обобщенного типа T
  interface IAggregate<T>
  {
    ...
  }

  // 2. Объявить интерфейс итератора для типа T
  interface IIterator<T>
  {
    ...
  }

  // 3. Конкретный итератор
  class ConcreteIterator1<T> : IIterator<T>
  {
    ...
  }

  // 4. Конкретный контейнер для элементов типа T
  class ConcreteAggregate1<T>:IAggregate<T>
  {
    ...
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Тестирование паттерна Iterator - код клиента
      // 1. Создать массив чисел
      double[] A = { 2.3, 3.2, 4.4, 5.1, 8.2 };

      // 2. Создать экземпляр контейнера на основе массива A
      ConcreteAggregate1<double> ag1 = new ConcreteAggregate1<double>(A);
      ag1.Print("ag1"); // вывод контейнера

      // 3. Создать итератор для экземпляра ag1
      ConcreteIterator1<double> it1 = (ConcreteIterator1<double>)ag1.CreateIterator();

      // 4. Демонстрация работы итератора
      Console.WriteLine("----------------------");
      Console.WriteLine("Access using iterator:");
      it1.First();
      while (!it1.IsDone())
      {
        Console.Write("{0} ", (double)it1.CurrentItem());
        it1.Next();
      }
      Console.WriteLine();
      Console.ReadKey();
    }
  }
}

После запуска, программа выдает следующий результат

ag1:
2.3 3.2 4.4 5.1 8.2
----------------------
Access using iterator:
2.3 3.2 4.4 5.1 8.2

 


Связанные темы