Книга: Погружение в Паттерны Проектирования
Назад: Команда
Дальше: Посредник
. Поэтому сама коллекция может создавать итераторы, так как она знает, какие именно итераторы могут с ней работать.

  • Конкретная коллекция возвращает новый экземпляр определённого конкретного итератора, связав его с текущим объектом коллекции. Обратите внимание, что сигнатура метода возвращает интерфейс итератора. Это позволяет клиенту не зависеть от конкретных классов итераторов.

  • Клиент работает со всеми объектами через интерфейсы коллекции и итератора. Так клиентский код не зависит от конкретного класса итератора, что позволяет применять различные итераторы, не изменяя существующий код программы.

    В общем случае клиенты не создают объекты итераторов, а получают их из коллекций. Тем не менее, если клиенту требуется специальный итератор, он всегда может создать его самостоятельно.

  • Псевдокод

    В этом примере паттерн Итератор используется для реализации обхода нестандартной коллекции, которая инкапсулирует доступ к социальному графу Facebook. Коллекция предоставляет несколько итераторов, которые могут по-разному обходить профиля людей.

    Структура классов примера паттерна Итератор

    Пример обхода социальных профилей через итератор.

    Так, итератор друзей перебирает всех друзей профиля, а итератор коллег — фильтрует друзей по принадлежности к компании профиля. Все итераторы реализуют общий интерфейс, который позволяет клиентам работать с профилями, не вникая в детали работы с социальной сетью (например, авторизацию, отправку REST-запросов и т.д.)

    Кроме того, Итератор избавляет код от привязки к конкретным классам коллекций. Это позволяет добавить поддержку другого вида коллекций (например, LinkedIn), не меняя клиентский код, который работает с итераторами и коллекциями.

    // Общий интерфейс коллекций должен определить фабричный
    // метод для производства итератора. Можно определить сразу
    // несколько методов, чтобы дать пользователям различные
    // варианты обхода одной и той же коллекции.
    interface SocialNetwork is
      method createFriendsIterator(profileId): ProfileIterator
      method createCoworkersIterator(profileId): ProfileIterator


    // Конкретная коллекция знает объекты каких итераторов
    // нужно создавать.
    class Facebook implements SocialNetwork is
      // ... Основной код коллекции ...

      // Код получения нужного итератора.
      method createFriendsIterator(profileId) is
        return new FacebookIterator(this, profileId, "friends")
      method createCoworkersIterator(profileId) is
        return new FacebookIterator(this, profileId, "coworkers")


    // Общий интерфейс итераторов.
    interface ProfileIterator is
      method getNext(): Profile
      method hasMore(): bool


    // Конкретный итератор.
    class FacebookIterator implements ProfileIterator is
      // Итератору нужна ссылка на коллекцию, которую
      // он обходит.
      private field facebook: Facebook
      private field profileIdtype: string

      // Но каждый итератор обходит коллекцию независимо от
      // остальных, поэтому он содержит информацию о текущей
      // позиции обхода.
      private field currentPosition
      private field cache: array of Profile

      constructor FacebookIterator(facebook, profileId, type) is
        this.facebook = network
        this.profileId = profileId
        this.type = type

      private method lazyInit() is
        if (cache == null)
          cache = facebook.sendSophisticatedSocialGraphRequest(profileId, type)

      // Итератор реализует методы базового
      // интерфейса по-своему.
      method getNext() is
        if (hasMore())
          currentPosition++
          return cache[currentPosition]

      method hasMore() is
        lazyInit()
        return cache.length < currentPosition


    // Вот ещё полезная тактика: мы можем передавать объект
    // итератора вместо коллекции в клиентские классы. При таком
    // подходе, клиентский код не будет иметь доступа к
    // коллекциям, а значит его не будет волновать подробности
    // их реализаций. Ему будет доступен только общий
    // интерфейс итераторов.
    class SocialSpammer is
      method send(iterator: ProfileIterator, message: string) is
        while (iterator.hasNext())
          profile = iterator.getNext()
          System.sendEmail(profile.getEmail(), message)


    // Класс приложение конфигурирует классы как захочет.
    class Application is
      field network: SocialNetwork
      field spammer: SocialSpammer

      method config() is
        if working with Facebook
          this.network = new Facebook()
        if working with LinkedIn
          this.network = new LinkedIn()
        this.spammer = new SocialSpammer()

      method sendSpamToFriends(profile) is
        iterator = network.createFriendsIterator(profile.getId())
        spammer.send(iterator, "Very important message")

      method sendSpamToCoworkers(profile) is
        iterator = network.createCoworkersIterator(profile.getId())
        spammer.send(iterator, "Very important message")

    Применимость

    Когда у вас есть сложная структура данных, и вы хотите скрыть от клиента детали её реализации (из-за сложности или вопросов безопасности).

    Итератор предоставляет клиенту всего несколько простых методов перебора элементов коллекции. Это не только упрощает доступ к коллекции, но и защищает её данные от неосторожных или злоумышленных действий.

    Когда вам нужно иметь несколько вариантов обхода одной и той же структуры данных.

    Нетривиальные алгоритмы обхода структуры данных могут иметь довольно объёмный код. Этот код будет захламлять всё вокруг, если поместить его в класс коллекции или где-то посреди основной бизнес-логики программы. Применив итератор, вы можете переместить код обхода структуры данных в собственный класс, упростив поддержку остального кода.

    Когда вам хочется иметь единый интерфейс обхода различных структур данных.

    Итератор позволяет вынести реализации различных вариантов обхода в подклассы. Это позволит легко взаимозаменять объекты итераторов, в зависимости от того, с какой структурой данных приходится работать.

    Шаги реализации

    1. Создайте интерфейс итераторов. В качестве минимума, вам понадобится операция получения следующего элемента. Но для удобства можно предусмотреть и другие методы, например, для получения предыдущего элемента, текущей позиции, проверки окончания обхода и прочих.

    2. Создайте интерфейс коллекции и опишите в нём метод получения итератора. Важно, чтобы его сигнатура возвращала общий интерфейс итераторов, а не один из конкретных итераторов.

    3. Создайте классы конкретных итераторов для тех коллекций, которые нужно обходить с помощью паттерна. Итератор должен быть привязан только к одному объекту коллекции. Обычно эта связь устанавливается через конструктор.

    4. Реализуйте методы получения итератора в конкретных классах коллекций. Они должны создавать новый итератор того класса, который способен работать с данным типом коллекции. Коллекция должна передавать собственную ссылку в созданный итератор.

    5. В клиентском коде и в классах коллекций не должно остаться кода обхода элементов. Клиент должен получать новый итератор из объекта коллекции каждый раз, когда ему нужно перебрать её элементы.

    Преимущества и недостатки

    Отношения с другими паттернами

    Назад: Команда
    Дальше: Посредник

    asd
    asdda