イテレータパターン
CoddyのLuaジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 64/70。
オブジェクト内に項目のコレクションが格納されている場合、それらをループで順に処理したいことがよくあります。Luaの汎用的なforループは、任意のイテレータ関数で動作します。各反復の前に関数を呼び出し、その関数がnilを返すと停止します。
Iteratorパターンでは、オブジェクトにそのような関数を返すメソッドを持たせます。その関数はローカル変数に位置を保持するため、その位置はコレクションではなく、1つのループに属します。
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
endbag: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 を格納し、自身のイテレーターとして機能すると、最初のループでインデックスが末尾に達するため、2回目のループでは何も出力されません。また、break で途中終了したループの後では、次のループが途中から始まってしまいます。
チャレンジ
簡単Luaの汎用forループで繰り返し処理できるPlaylistクラスを作りましょう!イテレータ関数を返す:iter()メソッドを使って、Iteratorパターンを実装します。
コードは2つのファイルに分けて書きます。
Playlist.lua:曲を内部に保存するPlaylistクラスを作成します。このクラスには、空の曲リストを初期化する:new()コンストラクタと、曲をリストに追加する:addSong(title)メソッドが必要です。最も重要なのは、呼び出されるたびに新しい関数を返す:iter()メソッドを追加することです。その関数はローカル変数で位置を保持し、呼び出されるたびに次の曲を返します。曲が残っていない場合はnilを返します。main.lua:Playlistモジュールを読み込んで、新しいプレイリストを作成します。受け取った入力に基づいて曲を追加し、playlist:iter()を使った汎用forループで各曲名を1行ずつ出力します。
: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')
-- 入力から曲名を3つ読み取る
local song1 = io.read()
local song2 = io.read()
local song3 = io.read()
-- TODO: Playlist:new() を使って新しいプレイリストを作成する
-- TODO: :addSong() を使って3曲をプレイリストに追加する
-- TODO: playlist:iter() に対して汎用 for ループを使用する
-- そして、各曲名をそれぞれ別の行に出力する
-- ヒント: for song in playlist:iter() do ... end
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Luaオンラインコンパイラ