Паттерн Iterator
Часть раздела Object Oriented Programming путешествия по Lua на Coddy — урок 64 из 70.
Когда у вас есть коллекция элементов, хранящихся внутри объекта, вы часто хотите перебрать их в цикле. Универсальный цикл for в Lua прекрасно работает с пользовательскими итераторами, и вы можете встроить эту функциональность непосредственно в свои классы.
Паттерн Итератор позволяет объекту управлять тем, как осуществляется обход его внутренних данных. Один из элегантных подходов использует метаметод __call, который делает объект вызываемым, подобно функции. Каждый вызов возвращает следующий элемент, пока они не закончатся.
local Collection = {}
Collection.__index = Collection
function Collection:new()
local obj = { items = {}, index = 0 }
setmetatable(obj, Collection)
return obj
end
function Collection:add(item)
table.insert(self.items, item)
end
function Collection:__call()
self.index = self.index + 1
return self.items[self.index]
end
Когда определен __call, вы можете выполнять итерацию с помощью цикла while или передать объект напрямую в универсальный for:
local bag = Collection:new()
bag:add("apple")
bag:add("banana")
for item in bag do
print(item)
end
Цикл многократно вызывает bag(). Когда __call возвращает nil (элементов больше нет), цикл останавливается. Этот паттерн инкапсулирует логику итерации внутри самого объекта, предоставляя вам полный контроль над тем, как — и в каком порядке — осуществляется доступ к элементам.
Задание
ЛегкоДавайте создадим класс Playlist, по которому можно будет итерироваться с помощью универсального цикла for в Lua! Вы реализуете паттерн «Итератор», используя метаметод __call, что позволит обходить ваш плейлист песня за песней.
Вы организуете свой код в двух файлах:
Playlist.lua: Создайте класс Playlist, который хранит песни внутри себя. Вашему классу нужен конструктор:new(), который инициализирует пустой список песен и устанавливает индекс итерации в 0. Добавьте метод:addSong(title)для добавления песен в плейлист. Самое главное, реализуйте метаметод__callтак, чтобы каждый раз, когда плейлист «вызывается», он возвращал следующую песню в списке (илиnil, когда песен больше нет).main.lua: Подключите модуль Playlist и создайте новый плейлист. Добавьте в него песни на основе полученных входных данных, а затем используйте универсальный циклforдля итерации по плейлисту и вывода названия каждой песни на отдельной строке.
Магия паттерна «Итератор» заключается в том, что ваш объект Playlist становится вызываемым — при использовании в цикле for Lua вызывает его многократно, пока он не вернет nil, предоставляя вам каждую песню по порядку.
Вы получите три входных значения, каждое из которых представляет собой название песни для добавления в ваш плейлист (по порядку).
Например, если входные данные — Bohemian Rhapsody, Stairway to Heaven и Hotel California, вывод должен быть следующим:
Bohemian Rhapsody
Stairway to Heaven
Hotel CaliforniaЕсли входные данные — Yesterday, Imagine и Let It Be, вывод должен быть следующим:
Yesterday
Imagine
Let It BeШпаргалка
Паттерн Итератор позволяет объекту управлять тем, как осуществляется обход его внутренних данных, путем реализации пользовательской логики итерации.
Используйте метаметод __call, чтобы сделать объект вызываемым как функцию. Каждый вызов возвращает следующий элемент, пока они не закончатся (возвращает nil):
local Collection = {}
Collection.__index = Collection
function Collection:new()
local obj = { items = {}, index = 0 }
setmetatable(obj, Collection)
return obj
end
function Collection:add(item)
table.insert(self.items, item)
end
function Collection:__call()
self.index = self.index + 1
return self.items[self.index]
end
С определенным __call вы можете использовать объект напрямую в универсальном цикле for:
local bag = Collection:new()
bag:add("apple")
bag:add("banana")
for item in bag do
print(item)
end
Цикл вызывает объект многократно. Когда __call возвращает nil, цикл автоматически останавливается.
Попробуйте сами
-- Подключите модуль Playlist
local Playlist = require('Playlist')
-- Прочитайте названия трех песен из входных данных
local song1 = io.read()
local song2 = io.read()
local song3 = io.read()
-- TODO: Создайте новый плейлист, используя Playlist:new()
-- TODO: Добавьте три песни в плейлист, используя :addSong()
-- TODO: Используйте универсальный цикл for для итерации по плейлисту
-- и выведите название каждой песни на отдельной строке
-- Подсказка: for song in playlist do ... end
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Концепция 'self'
Таблицы с функциямиЯвный 'self'Синтаксис с двоеточиемТочка против двоеточияИтоги — Движущаяся точка4Проект: Цифровой банк
Настройка проектаМетод пополнения7Полиморфизм и переопределение
Переопределение методовВызов родительских методовУтиная типизацияОбщий интерфейсПроверка типаИтоги — Роли сотрудников2Паттерн прототипа класса
Концепция прототипаСвязывание через __indexКонструктор :new()Инициализация атрибутовНезависимые экземплярыИтоги — Автозавод5Перегрузка операторов в ООП
Сложение объектовВычитание объектовКонкатенация объектовСравнение объектов (<, >)Повторение — Математика кошелька8Инкапсуляция
Соглашения об именованииЗамыкания для приватностиДоступ через замыканияТаблицы только для чтенияЛогика валидацииИтоги — Защищенное хранилище11Паттерны проектирования (Lite)
Фабричные функцииТаблица SingletonПаттерн IteratorObserver (Слушатель)Итоги — Logger Factory3Состояние и поведение объектов
Переменные экземпляраГеттерыСеттерыВычисляемые свойстваФорматирование строкПроверка на равенствоПовторение — Оценка студента6Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигур