Menu
Coddy logo textTech

Паттерн 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 (элементов больше нет), цикл останавливается. Этот паттерн инкапсулирует логику итерации внутри самого объекта, предоставляя вам полный контроль над тем, как — и в каком порядке — осуществляется доступ к элементам.

challenge icon

Задание

Легко

Давайте создадим класс 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
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Object Oriented Programming