Что делает итератор шаблоном дизайна?

9

Мне было интересно, что же делает Итератор особенным по сравнению с другими подобными конструкциями, и это заставило Банду Четырех перечислить его как шаблон проектирования.

Итератор основан на полиморфизме (иерархия коллекций с общим интерфейсом) и разделении задач (итерации по коллекциям должны быть независимы от структуры данных).

Но что, если мы заменим иерархию коллекций, например, иерархией математических объектов (целое число, число с плавающей запятой, комплекс, матрица и т. Д.), А итератор - классом, представляющим некоторые связанные операции над этими объектами, например, степенные функции. Диаграмма классов будет такой же.

Возможно, мы могли бы найти еще много похожих примеров, таких как Writer, Painter, Encoder и, возможно, лучших, которые работают одинаково. Однако я никогда не слышал, чтобы кто-либо из них назывался Design Pattern.

Так что же делает Итератор особенным?

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


Чтобы прояснить мою точку зрения, позвольте мне привести более подробный пример.

Вот наша проблема дизайна:

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

Вот разумное решение для нашей проблемы проектирования (практически обобщение шаблона итератора):

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

Пример:

Есть базовый класс или интерфейс MathObject(глупое имя, я знаю, может, у кого-то есть идея получше) с производными классами MyIntegerи MyMatrix. Для каждого должна быть определена MathObjectоперация Power, которая позволяет вычислять квадрат, куб и так далее. Таким образом, мы могли бы написать (на Java):

MathObject i = new MyInteger(5);
Power powerOfFive = i.getPower();
MyInteger square = powerOfFive.calculate(2); // should return 25
MyInteger cube = powerOfFive.calculate(3); // should return 125
Фрэнк Пуффер
источник
5
«Диаграмма классов была бы такой же» - и что? Шаблон проектирования - это не диаграмма классов. Это абстракция высокого уровня для класса решений повторяющейся проблемы.
Док Браун
@DocBrown: Правильно, но разве математические операции, запись объектов в файл, графический вывод или кодирование данных не повторяются, как итерации?
Фрэнк Пуффер
Выбор Design Pattern является субъективным (то есть в глазах «дизайнеров» или людей, которые судят о дизайне). Наименование шаблонов проектирования должно быть независимым от предметной области (чтобы мы не отвлекались, думая, что оно зависит от предметной области). Просто мое мнение, я не имею никаких ссылок, чтобы процитировать.
Rwong
@FrankPuffer Если вы наметите общее решение для записи объектов в файл, вы можете записать свое решение и назвать его «Шаблон записи объектов», если это полезно.
Брандин
3
Вы думаете об этом. Шаблон проектирования - это хорошо известное решение распространенной вычислительной проблемы, и это все, что нужно. Вы используете шаблон, когда можете распознать и применить преимущества, которые он предоставляет.
Роберт Харви

Ответы:

9

Большинство шаблонов из книги GoF имеют следующие общие черты:

  • они решают основные задачи проектирования, используя объектно-ориентированные средства
  • люди часто сталкиваются с подобными проблемами в произвольных программах независимо от сферы деятельности или бизнеса
  • они являются рецептами для того, чтобы сделать код более пригодным для повторного использования, часто делая его более твердым
  • они представляют канонические решения этих проблем

Проблемы, решаемые этими шаблонами, настолько просты, что многие разработчики понимают их главным образом как обходные пути для отсутствующих возможностей языка программирования , что, по-моему, является верной точкой зрения (обратите внимание, что книга GoF относится к 1995 году, где Java и C ++ не предлагали так много особенности как сегодня).

Шаблон итератора хорошо вписывается в это описание: он решает основную проблему, которая возникает очень часто, независимо от какой-либо конкретной области, и, как вы сами написали, это хороший пример для «разделения интересов». Как вы наверняка знаете, прямая поддержка итераторов - это то, что вы сегодня найдете во многих современных языках программирования.

Теперь сравните это с проблемами, которые вы выбрали:

  • запись в файл - это ИМХО просто не достаточно "базовый". Это очень специфическая проблема. Не существует и хорошего канонического решения - существует множество разных подходов к записи в файл, и нет четкой «наилучшей практики».
  • Painter, Encoder: что бы вы ни имели в виду, эти проблемы кажутся мне еще менее важными и даже не зависящими от предметной области.
  • наличие функции «power» для различных типов объектов: на первый взгляд, это может стоить того, чтобы быть шаблоном, но предложенное вами решение не убеждает меня - это больше похоже на попытку включить функцию power в нечто похожее на образец итератора. Я реализовал много кода с помощью инженерных вычислений, но я не могу вспомнить ситуацию, когда подход, подобный вашему объекту степенной функции, помог бы мне (однако, итераторы - это то, с чем мне приходится сталкиваться ежедневно).

Более того, я не вижу в вашем примере степенной функции ничего такого, что не могло бы быть истолковано как применение шаблона стратегии или шаблона команды, что означает, что эти основные части уже есть в книге GoF. Лучшее решение может содержать методы перегрузки операторов или расширения, но это те вещи, которые зависят от языковых возможностей, и это именно то, что «ОО означает», используемый «бандой», не может обеспечить.

Док Браун
источник
The problems solved by these patterns are so basic that many developers think their main purpose is to be workarounds for missing programming language features- Ирония в том, что разработчики программного обеспечения регулярно используют шаблоны проектирования программного обеспечения, которым 20 лет, и при этом все еще верят, что они пишут современный код.
Роберт Харви
@RobertHarvey: Я не думаю, что многие разработчики реализовали бы шаблон итератора сегодня по «пути OO», предложенному GoF. Они реализуют это обычно средствами, предоставляемыми языком или его стандартной библиотекой (например, в C # с помощью IEnumerableи yield). Но для других паттернов GoF то, что вы написали, может быть правдой.
Док Браун
1
Относится к workarounds for missing programming language features: blog.plover.com/prog/johnson.html
jrw32982 поддерживает Monica
8

«Банда четырех» цитирует определение паттерна Кристофера Александра:

Каждый шаблон описывает проблему, которая возникает снова и снова в нашей среде, а затем описывает ядро ​​решения этой проблемы […]

Какую проблему решают итераторы?

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

...

Применимость: используйте шаблон Iterator

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

Таким образом, можно утверждать, что шаблон итератора по определению зависит от области для коллекций. И это совершенно нормально. Другие шаблоны, такие как шаблон интерпретатора, зависят от домена для конкретных доменов языков, фабричные шаблоны зависят от домена для создания объектов,…. Конечно, это довольно глупое понимание «предметной области». Пока это повторяющаяся пара проблема-решение, мы можем назвать это шаблоном.

И хорошо, что шаблон Iterator существует. Плохие вещи случаются, если вы им не пользуетесь. Мой любимый анти-пример - Perl. Здесь каждая коллекция (массив или хэш) включает в себя состояние итератора как часть коллекции. Почему это плохо? Мы можем легко перебрать хеш с циклом while-each:

while (my ($key, $value) = each %$hash) {
  say "$key => $value";
}

Но что, если мы вызываем функцию в теле цикла?

while (my ($key, $value) = each %$hash) {
  do_something_with($key, $value, $hash);
}

Эта функция теперь может делать почти все, что ей нужно, кроме:

  • добавлять или удалять записи хеша, поскольку они непредсказуемо изменят порядок итераций (на языке C ++ - это сделает итератор недействительным).
  • повторять ту же хеш-таблицу без копирования, так как она будет использовать то же состояние итерации. К сожалению.

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

Да, конечно, шаблон итератора связан с другими шаблонами. Например, как создается экземпляр итератора? В Java у нас есть универсальный Iterable<T>и Iterator<T>интерфейс. Конкретное итерируемое подобное ArrayList<T>создает определенный тип итератора, тогда как a HashSet<T>может предоставлять совершенно другой тип итератора. Это очень напоминает мне абстрактный шаблон фабрики, где Iterable<T>абстрактная фабрика Iterator- это продукт.

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

Шаблоны не существуют изолированно, независимо друг от друга. Некоторые шаблоны - это разные решения одной и той же проблемы, а некоторые шаблоны описывают одно и то же решение в разных контекстах. Некоторые модели подразумевают другое. Кроме того, пространство шаблонов не закрывается, когда вы переворачиваете последнюю страницу книги «Шаблоны проектирования» (см. Также ваш предыдущий вопрос : «Банда четырех» тщательно исследовала «Пространство шаблонов»? ). Шаблоны, описанные в книге «Шаблоны проектирования», очень гибкие и широкие, открыты для бесконечных вариаций и, безусловно, не являются единственными существующими шаблонами.

Концепции, которые вы перечисляете (написание, рисование, кодирование), не являются шаблонами, потому что они не описывают комбинацию проблема-решение. Задача типа «мне нужно записать данные» или «мне нужно кодировать данные» на самом деле не является проблемой проектирования и не включает в себя решение; «решение», которое состоит только из «я знаю, я создам класс Writer», не имеет смысла. Но если у нас возникнет проблема типа «Я не хочу, чтобы на экране была нарисована половина графики, то может существовать шаблон:« Я знаю, я буду использовать графику с двойной буферизацией! »

Амон
источник
Отличный ответ, спасибо. Тем не менее, я не совсем уверен, что то, что вы пишете в последнем абзаце, применимо и здесь. Я отредактировал свой вопрос, чтобы объяснить, что я имею в виду.
Фрэнк Пуффер