Menu
CoddyTech

迭代器模式

本课属于 Coddy Lua 学习旅程的“面向对象编程”单元。第 64 课,共 70 课。

当对象中存储了一组项目时,你通常会想遍历它们。Lua 的通用 for 循环适用于任何迭代器函数:它会在每次循环前调用该函数,并在函数返回 nil 时停止。

迭代器模式为对象提供一个返回此类函数的方法。该函数会将位置保存在局部变量中,因此这个位置属于某个循环,而不是集合:

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

每次调用 bag:iter() 都会创建一个新函数,并拥有自己的 index,初始值为 0。这个函数是一个闭包:即使 iter 已经返回,它仍然可以使用 index 和 self。超过最后一项后,self.items[index] 的值为 nil,循环就会停止:

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

for item in bag:iter() do
    print(item)  -- 先输出 apple,再输出 banana
end

for item in bag:iter() do
    print(item)  -- 再次先输出 apple,再输出 banana
end

不要把位置保存在集合本身中。如果对象保存了 self.index 并充当自己的迭代器,那么第一个循环结束后,索引就会停在末尾,因此第二个循环不会打印任何内容;如果循环因 break 提前停止,下一个循环就会从中间开始。

challenge icon

挑战

简单

让我们来编写一个 Playlist 类,你可以用 Lua 的泛型 for 循环遍历它!你将使用 :iter() 方法实现迭代器模式,该方法会返回一个迭代器函数。

你会把代码分别写在两个文件中:

  • Playlist.lua:创建一个在内部存储歌曲的 Playlist 类。这个类需要一个 :new() 构造函数,用于初始化一个空歌曲列表;还需要一个 :addSong(title) 方法,用于向列表中添加歌曲。最重要的是,添加一个 :iter() 方法,每次调用时都会返回一个新函数。该函数会在一个局部变量中保存当前位置,每次调用时返回下一首歌曲;如果没有更多歌曲,则返回 nil。
  • main.lua:导入 Playlist 模块并创建一个新播放列表。根据收到的输入向列表中添加歌曲,然后使用泛型 for 循环遍历 playlist:iter(),逐行打印每首歌曲的标题。

由于每次调用 :iter() 都会从第一首歌曲开始,因此你可以多次遍历同一个播放列表。

你会收到 3 个输入,每个输入表示要按顺序添加到播放列表中的一首歌曲的标题。

例如,如果输入是 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 循环遍历 playlist:iter()
-- 并将每首歌的歌名分别打印在单独一行
-- 提示:for song in playlist:iter() do ... end
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Lua 在线编译器