Menu
CoddyTech

Padrão Iterator

Parte da seção Programação orientada a objetos do Journey de Lua da Coddy. Lição 64 de 70.

Quando você tem uma coleção de itens armazenada dentro de um objeto, muitas vezes quer percorrê-los em um loop. O loop genérico for do Lua funciona com qualquer função iteradora: ele chama a função antes de cada iteração e para quando a função retorna nil.

O padrão Iterador fornece ao objeto um método que retorna uma função desse tipo. A função mantém sua posição em uma variável local, então a posição pertence a um loop, não à coleção:

local Collection = {}
Collection.__index = Collection

function Collection:new()
    local obj = { items = {} }
    setmetatable(obj, Collection)
    return obj
end

function Collection:add(item)
    table.insert(self.items, item)
end

function Collection:iter()
    local index = 0
    return function()
        index = index + 1
        return self.items[index]
    end
end

Cada chamada a bag:iter() cria uma nova função com seu próprio index, começando em 0. A função é uma closure: ela ainda pode usar index e self depois que iter retorna. Depois do último item, self.items[index] é nil, e o loop para:

local bag = Collection:new()
bag:add("apple")
bag:add("banana")

for item in bag:iter() do
    print(item)  -- apple, depois banana
end

for item in bag:iter() do
    print(item)  -- apple, depois banana novamente
end

Mantenha a posição fora da própria coleção. Se o objeto armazenasse self.index e atuasse como seu próprio iterador, o primeiro loop deixaria o índice no final, então um segundo loop não imprimiria nada, e um loop interrompido antes do fim com break faria o próximo loop começar no meio.

challenge icon

Desafio

Fácil

Vamos criar uma classe Playlist que você pode percorrer com o loop genérico for do Lua! Você vai implementar o padrão Iterator com um método :iter() que retorna uma função iteradora.

Você vai organizar seu código em dois arquivos:

  • Playlist.lua: Crie uma classe Playlist que armazena músicas internamente. Sua classe precisa de um construtor :new() que inicializa uma lista de músicas vazia e de um método :addSong(title) que adiciona uma música a ela. Mais importante, adicione um método :iter() que retorna uma nova função cada vez que é chamado. Essa função mantém sua posição em uma variável local e retorna a próxima música a cada chamada, ou nil quando não houver mais músicas.
  • main.lua: Importe seu módulo Playlist e crie uma nova playlist. Adicione músicas a ela com base nas entradas recebidas e, em seguida, use um loop genérico for sobre playlist:iter() para imprimir o título de cada música em sua própria linha.

Como cada chamada a :iter() começa na primeira música, a mesma playlist pode ser percorrida quantas vezes você quiser.

Você receberá três entradas, cada uma representando o título de uma música a ser adicionada à sua playlist (na ordem).

Por exemplo, se as entradas forem Bohemian Rhapsody, Stairway to Heaven e Hotel California, a saída deverá ser:

Bohemian Rhapsody
Stairway to Heaven
Hotel California

Se as entradas forem Yesterday, Imagine e Let It Be, a saída deverá ser:

Yesterday
Imagine
Let It Be

Experimente você mesmo

-- Carregue o módulo Playlist
local Playlist = require('Playlist')

-- Leia três títulos de músicas da entrada
local song1 = io.read()
local song2 = io.read()
local song3 = io.read()

-- TODO: Crie uma nova lista de reprodução usando Playlist:new()

-- TODO: Adicione as três músicas à lista de reprodução usando :addSong()

-- TODO: Use um laço for genérico com playlist:iter()
-- e imprima cada título de música em uma linha separada
-- Dica: for song in playlist:iter() do ... end
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação orientada a objetos

Pratique por conta própria: Compilador de Lua online