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

 


Зв’язані теми