イテレータパターン
CoddyのLuaジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 64/70。
オブジェクト内に格納された item の collection がある場合、それらを loop したいことがよくあります。Lua の汎用 for loop はカスタムイテレータと美しく連携し、この機能をクラスに直接組み込むことができます。
Iterator パターンを使用すると、オブジェクトは内部データをどのように走査するかを制御できます。洗練されたアプローチの 1 つでは、オブジェクトを関数のように呼び出し可能にする __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 loop を使って iterate したり、オブジェクトを直接 generic for に渡したりできます:
local bag = Collection:new()
bag:add("apple")
bag:add("banana")
for item in bag do
print(item)
end
loop は bag() を繰り返し呼び出します。__call が nil(これ以上 item がない)を返すと、loop は停止します。この pattern は iteration のロジックをオブジェクト自体の内部にカプセル化し、items にアクセスする方法と順序を完全に制御できるようにします。
チャレンジ
簡単Lua の汎用 for ループを使って反復処理できる Playlist class を作りましょう!__call metamethod を使って Iterator pattern を実装し、プレイリストを曲ごとにたどれるようにします。
コードを2つのファイルに分けて整理します。
Playlist.lua: 曲を内部に保存する Playlist class を Create します。class には、空の曲のリストを初期化し、iteration index を 0 に設定する:new()Constructor が必要です。プレイリストに曲を追加するための:addSong(title)method を Add します。最も重要なのは__callmetamethod を Implement することです。これにより、プレイリストが "called" されるたびに、リスト内の次の曲(これ以上曲がない場合はnil)を返します。main.lua: Playlist module を Require して、新しいプレイリストを Create します。受け取った入力に基づいて曲を追加し、汎用forloop を使ってプレイリストを iterate し、各曲のタイトルをそれぞれ別の行に print します。
Iterator pattern の魅力は、Playlist オブジェクトが callable になることです。for loop で使用すると、Lua は nil を返すまでそれを繰り返し呼び出すため、各曲を順番に取得できます。
プレイリストに追加する曲のタイトルを表す入力を、順番に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: ジェネリックforループを使ってプレイリストを反復処理する
-- そして各曲のタイトルをそれぞれ1行に出力する
-- Hint: for song in playlist do ... end
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Luaオンラインコンパイラ