Menu
Coddy logo textTech

Паттерн Iterator

Часть раздела Объектно-ориентированное программирование путешествия по Lua на Coddy. Урок 64 из 70.

Когда у вас есть коллекция элементов, хранящихся внутри объекта, вы часто хотите перебрать их. Универсальный цикл for в Lua отлично работает с пользовательскими итераторами, и вы можете встроить эту функциональность непосредственно в свои классы.

Паттерн Iterator позволяет объекту управлять тем, как перебираются его внутренние данные. Один из элегантных подходов использует метаметод __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 или напрямую передать объект в generic 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, через который можно выполнять итерацию с помощью generic for loop Lua! Вы реализуете паттерн Iterator, используя метаметод __call, благодаря чему можно будет проходить по Playlist песня за песней.

Вы организуете код в двух файлах:

  • Playlist.lua: Создайте класс Playlist, который хранит песни внутри себя. Вашему классу нужен конструктор :new(), инициализирующий пустой список песен и устанавливающий индекс iteration в 0. Добавьте метод :addSong(title) для добавления песен в Playlist. Самое главное — реализуйте метаметод __call, чтобы каждый раз, когда Playlist «called», он возвращал следующую песню из списка (или nil, когда больше нет песен).
  • main.lua: Подключите свой module Playlist и создайте новый Playlist. Добавьте в него песни на основе полученных входных данных, затем используйте generic for loop для прохода по Playlist и вывода названия каждой песни на отдельной строке.

Магия паттерна Iterator заключается в том, что ваш объект Playlist становится вызываемым: при использовании в for loop Lua repeatedly вызывает его, пока он не вернёт nil, предоставляя вам каждую песню по порядку.

Вы получите три входных значения, каждое из которых представляет название песни, которую нужно добавить в Playlist (в указанном порядке).

Например, если входные данные — Bohemian Rhapsody, Stairway to Heaven и Hotel California, результатом должен быть:

Bohemian Rhapsody
Stairway to Heaven
Hotel California

Если входные данные — Yesterday, Imagine и Let It Be, результатом должен быть:

Yesterday
Imagine
Let It Be

Попробуйте сами

-- Подключить модуль 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Проверьте себя

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

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Lua