Есть 23 классических шаблона проектирования, которые описаны в оригинальной книге Design Patterns: Elements of Reusable Object-Oriented Software
. Эти шаблоны предоставляют решения конкретных проблем, которые часто повторяются при разработке программного обеспечения.
В этой статье я собираюсь описать, что такое шаблон итератора; и как и когда его следует применять.
Шаблон итератора: основная идея
В объектно-ориентированном программировании шаблон итератора - это шаблон проектирования, в котором итератор используется для обхода контейнера и доступа к его элементам. Шаблон итератора отделяет алгоритмы от контейнеров; в некоторых случаях алгоритмы обязательно зависят от контейнера и поэтому не могут быть разделены. - Википедия
Обеспечивает способ последовательного доступа к элементам агрегированного объекта
без раскрытия его базового представления. - Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования
Основная особенность этого шаблона заключается в том, что он позволяет вам перемещаться по элементам коллекции, не раскрывая ее базовое представление (массив, карта, дерево и т. Д.). Таким образом, этот шаблон решает две проблемы:
- Позволяет изменить внутреннюю реализацию коллекции без изменения реализации алгоритма.
- Позволяет добавлять новые алгоритмы, которые работают со всеми существующими типами коллекций.
Подводя итог, шаблон итератора скрывает внутреннюю реализацию коллекции от клиента. Схема UML этого шаблона следующая:
Класс Iterator
- это интерфейс, который определяет различные операции перехода к коллекции (next
или hasNext
), в то время как этот класс Aggregate
будет создавать Iterator
. Наконец, система будет использовать ConcreteAggregate
и ConcreteIterator
.
- Ваша коллекция имеет сложную структуру данных под капотом, но вы хотите скрыть ее сложность от клиентов.
- Вам необходимо уменьшить дублирование кода обхода в вашем приложении.
- Вы хотите, чтобы ваш код мог перемещаться по различным структурам данных.
Шаблон итератора имеет несколько преимуществ, которые можно обобщить в следующих пунктах:
- Код проще в использовании, понимании и тестировании, поскольку итератор использует принципы единой ответственности и открытого / закрытого SOLID.
- Принцип единой ответственности позволяет нам очистить клиента и коллекции алгоритмов обхода.
- Принцип открытости / закрытости позволяет реализовать новые типы коллекций и итераторов, ничего не нарушая.
- Параллельная итерация по одной и той же коллекции, поскольку каждый объект итератора содержит собственное состояние итерации.
- Чистый код, потому что клиент / контекст не использует сложный интерфейс, а система более гибкая и многоразовая.
Теперь я покажу вам, как вы можете реализовать этот шаблон с помощью JavaScript / TypeScript. В нашем случае я создал проблему, в которой есть класс с именем WordsCollection
, который определяет список слов (items
) и его набор методов для получения и добавления (getItems
и addItem
). Этот класс используется client
с помощью управляющих структур, таких как for
или forEach
. На следующей диаграмме UML показан сценарий, который я только что описал.
Ассоциированные коды WordsCollection
следующие:
Ассоциированные коды client
следующие:
Основная проблема в этом решении заключается в том, что код связан. Это означает, что клиент должен знать, как внутренняя структура коллекции реализует два пройденных метода (Straight
и Reverse
). Представьте, что вам нужно изменить структуру данных с Array
на Map
, тогда код, связанный с клиентом, ломается из-за связи. Другой интересный вариант использования шаблона Iterator - это когда вам нужен новый способ итерации коллекции, например AlphabeticalOrdered.
Решение состоит в том, чтобы использовать шаблон итератора, и новая диаграмма UML, использующая этот шаблон, показана ниже:
Следовательно, решение состоит из класса интерфейса (Iterator
), который определяет метод обхода коллекции:
- current (): T.
- key (): число.
- hasMoreElements (): логическое.
- перемотка: недействительно.
Класс 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 г.