Итераторы
Второй пример демонстрирует наследование программ общего вида, а не константных атрибутов.
Предположим, мы хотим обеспечить общий механизм, позволяющий просмотр всех элементов (итерирование) некоторой структуры данных, например линейных структур, таких как списки. "Итерирование" означает выполнение некоторой процедуры, скажем, action, на элементах этой структуры, просматриваемых в последовательном порядке. Нам хочется обеспечить несколько различных механизмов итерирования, включающих применение action ко всем элементам, удовлетворяющим условию, заданному булевой функцией test, ко всем элементам до появления первого, удовлетворяющего test, или до первого, не удовлетворяющего этой функции. Ну и так далее, вариантов можно придумать много. Система, использующая этот механизм, должна быть способна применять его к произвольным компонентам action и test.
С первого взгляда может показаться, что итерирующие компоненты должны принадлежать классам, описывающих соответствующие структуры данных, таким как LIST или SEQUENCE.
В упражнении У6.7 предлагается показать, что это неправильное решение.Предпочтительнее ввести независимую иерархию итераторов, показанную на рис. 6.11.
Рис. 6.11. Иерархия итераторов
Класс LINEAR_ITERATOR, один из наиболее интересных классов в этом обсуждении, выглядит так:
indexing description: "Объекты, допускающие итерирование на линейных структурах" names: iterators, iteration, linear_iterators, linear_iteration deferred class LINEAR_ITERATOR [G] inherit ITERATOR [G] redefine target end feature -- Access invariant_value: BOOLEAN is -- Свойство, сопровождающее итерацию (по умолчанию: true) do Result:= True end target: LINEAR [G] -- Структура, к которой будут применяться компоненты итерации test: BOOLEAN is -- Булево условие выбора применимых элементов deferred end feature - Basic operations action is -- Действие на выбранных элементах deferred end do_if is -- Применить action в последовательности к каждому элементу --target, удовлетворяющему test.do from start invariant invariant_value until exhausted loop if test then action end forth end ensure then exhausted end ... И так далее: do_all, do_while, do_until и другие процедуры ... endРассмотрим теперь класс, нуждающийся в выполнении некоторой операции над выбранными элементами списка специального типа. Например, это может быть командный класс в системе обработки текстов, нуждающийся в проверке всех абзацев документа, за исключением специально отформатированных (подобных абзацам с текстом программ). Тогда:
class JUSTIFIER inherit LINEAR_ITERATOR [PARAGRAPH] rename action as justify, test as justifiable, do_all as justify_all end feature justify is do ... end justifiable is -- Подлежит ли абзац проверке? do Result := not preformated end ... endПереименование облегчает понимание. Заметьте, нет необходимости в объявлении или повторном объявлении процедуры justify_all (бывшей do_all): будучи наследуемой, ожидаемая работа будет проделана эффективными версиями action и test.
Процедура justify, вместо того чтобы быть описанной в классе, может наследоваться от другого родителя. В этом случае множественного наследования будет выполняться операция объединения ("join"), эффективизирующая отложенную action, наследуемую от одного родителя под именем justify (здесь переименование существенно), с эффективной justify, наследуемой от другого родителя. Реально, это и есть брак по расчету.
Класс LINEAR_ITERATOR является замечательным примером класса поведения (behavior class), рассматривая общее поведение и оставляя открытыми специфические компоненты, так чтобы его потомки могли подключить специальные варианты.