Есть 23 классических шаблона проектирования, которые описаны в оригинальной книге Design Patterns: Elements of Reusable Object-Oriented Software. Эти шаблоны предоставляют решения конкретных проблем, которые часто повторяются при разработке программного обеспечения.

В этой статье я собираюсь описать, что такое шаблон итератора; и как и когда его следует применять.

Шаблон итератора: основная идея

В объектно-ориентированном программировании шаблон итератора - это шаблон проектирования, в котором итератор используется для обхода контейнера и доступа к его элементам. Шаблон итератора отделяет алгоритмы от контейнеров; в некоторых случаях алгоритмы обязательно зависят от контейнера и поэтому не могут быть разделены. - Википедия

Обеспечивает способ последовательного доступа к элементам агрегированного объекта
без раскрытия его базового представления. - Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования

Основная особенность этого шаблона заключается в том, что он позволяет вам перемещаться по элементам коллекции, не раскрывая ее базовое представление (массив, карта, дерево и т. Д.). Таким образом, этот шаблон решает две проблемы:

  1. Позволяет изменить внутреннюю реализацию коллекции без изменения реализации алгоритма.
  2. Позволяет добавлять новые алгоритмы, которые работают со всеми существующими типами коллекций.

Подводя итог, шаблон итератора скрывает внутреннюю реализацию коллекции от клиента. Схема UML этого шаблона следующая:

Класс Iterator - это интерфейс, который определяет различные операции перехода к коллекции (next или hasNext), в то время как этот класс Aggregate будет создавать Iterator. Наконец, система будет использовать ConcreteAggregate и ConcreteIterator.

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

Шаблон итератора имеет несколько преимуществ, которые можно обобщить в следующих пунктах:

  • Код проще в использовании, понимании и тестировании, поскольку итератор использует принципы единой ответственности и открытого / закрытого SOLID.
  • Принцип единой ответственности позволяет нам очистить клиента и коллекции алгоритмов обхода.
  • Принцип открытости / закрытости позволяет реализовать новые типы коллекций и итераторов, ничего не нарушая.
  • Параллельная итерация по одной и той же коллекции, поскольку каждый объект итератора содержит собственное состояние итерации.
  • Чистый код, потому что клиент / контекст не использует сложный интерфейс, а система более гибкая и многоразовая.

Теперь я покажу вам, как вы можете реализовать этот шаблон с помощью JavaScript / TypeScript. В нашем случае я создал проблему, в которой есть класс с именем WordsCollection, который определяет список слов (items) и его набор методов для получения и добавления (getItems и addItem). Этот класс используется client с помощью управляющих структур, таких как for или forEach. На следующей диаграмме UML показан сценарий, который я только что описал.

Ассоциированные коды WordsCollection следующие:

Ассоциированные коды client следующие:

Основная проблема в этом решении заключается в том, что код связан. Это означает, что клиент должен знать, как внутренняя структура коллекции реализует два пройденных метода (Straight и Reverse). Представьте, что вам нужно изменить структуру данных с Array на Map, тогда код, связанный с клиентом, ломается из-за связи. Другой интересный вариант использования шаблона Iterator - это когда вам нужен новый способ итерации коллекции, например AlphabeticalOrdered.

Решение состоит в том, чтобы использовать шаблон итератора, и новая диаграмма UML, использующая этот шаблон, показана ниже:

Следовательно, решение состоит из класса интерфейса (Iterator), который определяет метод обхода коллекции:

  1. current (): T.
  2. key (): число.
  3. hasMoreElements (): логическое.
  4. перемотка: недействительно.

Класс AlphabeticalOrderIterator - это итератор, который отвечает за реализацию методов для правильного обхода коллекции. Итератору нужна коллекция (WordsCollection), использующая агрегацию и способ итерации (обратный или прямой). Итак, код, связанный с AlphabeticalOrderIterator, следующий:

Следующий шаг состоит из определения Aggregator интерфейса и модификации коллекции для реализации этого интерфейса. Итак, код, связанный с Aggregator, следующий:

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

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

Клиент отделен от внутренней структуры WordsCollection class (Single Responsibility), и вы можете расширить программное обеспечение, добавив новые итераторы (Open / Closed).

Я создал несколько скриптов npm, которые запускают показанные здесь примеры кода после применения паттерна фасада.

npm run example1-problem
npm run example1-iterator-solution-1

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

Сначала у нас есть контакты из двух известных социальных сетей: Dev.to и Medium (нам не нужно уточнять, какая из них моя любимая, вы все это знаете! :-)). Реализация структуры данных в каждой из социальных сетей различается, поскольку в Dev.to для поддержания контактов используется массив, а в Medium - карта.

Шаблон итератора позволит нам полностью отделить код от наших контактов и социальных сетей, что позволит нам абстрагироваться от внутренних реализаций каждой социальной сети и даже иметь возможность добавлять новые социальные сети (хотя… даже существуют для нас выродки?: P).

Ниже вы можете найти гифку, на которой клиент использует всю нашу структуру (я сделал небольшой пример интерфейса командной строки).

На следующей диаграмме UML вы можете увидеть предлагаемое решение этой проблемы:

Хорошо, модель в этой задаче - не строка, а профиль пользователя, как вы можете видеть в следующем коде:

В классе Profile у нас есть метод getContactsByType, который возвращает контакты друга или коллеги.

Следующим шагом является определение интерфейса итератора (ProfileIterator) и интерфейса агрегатора (SocialNetwork), которые определяют методы, которые должны быть реализованы каждым итератором и агрегатором.

Поэтому код, связанный с этими интерфейсами, следующий:

Теперь нам нужно реализовать конкретную реализацию предыдущих интерфейсов, чтобы решить нашу проблему. Первой социальной сетью, которую мы решим, будет Dev.to. Реализация агрегатора и итератора показана ниже.

Обратите внимание, что коллекция, в которой хранятся контакты, представляет собой массив и реализованы createFriendsIterator и createCoworkersIterator. В нем есть несколько методов, имитирующих подключение к удаленному API для получения контактов.

Код, связанный с классом DevToIterator, следующий:

Самая важная часть предыдущего кода - это реализация интерфейса. Конкретная реализация основана на внутренней структуре данных коллекции (Array). Вы можете заметить, что я разработал ленивый метод запроса контактов (подумайте об этом внимательно. Если я запрашиваю всех друзей у друга, это может привести к бесконечному циклу).

Что ж, на этом этапе мы должны создать наш SocialSpammer класс, который использует только интерфейсы. Класс SocialSpammer отделен от любого конкретного класса, как вы можете видеть в следующем коде:

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

Теперь мы можем использовать код в следующем клиенте:

Теперь пришло время проверить, можем ли мы использовать принцип открытости / закрытости, создав новую социальную сеть и ее итератор, не нарушая работу нашего приложения.

Код, связанный с классом medium, следующий:

Мы могли бы использовать наследование для упрощения кода между Dev.to и Medium, но, чтобы не расширять этот пост, мы предпочли повторить код. Вы можете видеть, что класс Medium использует другую структуру данных для хранения контактов.

Наконец, medium-iterator это следующий:

Я создал сценарий npm, который запускает показанный здесь пример после применения шаблона Iterator и интерфейса командной строки.

npm run example2-iterator-solution1

Шаблон итератора позволяет избежать связанного кода в ваших проектах. Когда в коллекции несколько алгоритмов и структур данных, шаблон итератора отлично адаптируется. Ваш код будет чище, поскольку вы примените два известных принципа, например Единственная ответственность и Открытие / закрытие.

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

Первоначально опубликовано на https://www.carloscaballero.io 12 июня 2019 г.