迭代器模式
本课属于 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 提前停止,下一个循环就会从中间开始。
挑战
简单让我们来编写一个 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
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Lua 在线编译器