tl; dr:グローバルテーブルに影響を与えることなく、いくつかの情報を共有する必要がある複数のファイルにLuaコードを分割できるデザインパターンは何ですか?
バックグラウンド
ライブラリの要求がグローバル名前空間に影響を与えるLuaでライブラリを作成することは、悪い形式と見なされます。
--> somelib.lua <--
SomeLib = { ... }
--> usercode.lua <--
require 'somelib'
print(SomeLib) -- global key created == bad
代わりに、ローカル変数を使用するライブラリを作成し、ユーザーが適切と考えるように割り当てるためにそれらを返すことがベストプラクティスと見なされます。
--> somelib.lua <--
local SomeLib = { ... }
return SomeLib
--> usercode.lua <--
local theLib = require 'somelib' -- consumers name lib as they wish == good
上記のパターンは、単一のファイルを使用する場合に正常に機能します。ただし、相互に参照する複数のファイルがある場合、これはかなり困難になります。
具体例
アサーションがすべて合格するように、次の一連のファイルをどのように書き直すことができますか?理想的には、書き換えによってディスク上に同じファイルが残り、各ファイルの責任が残ります。(すべてのコードを1つのファイルにマージして書き直すことは効果的ですが、役に立ちません;)
--> test_usage.lua <--
require 'master'
assert(MASTER.Simple)
assert(MASTER.simple)
assert(MASTER.Shared)
assert(MASTER.Shared.go1)
assert(MASTER.Shared.go2)
assert(MASTER.Simple.ref1()==MASTER.Multi1)
assert(pcall(MASTER.Simple.ref2))
assert(_G.MASTER == nil) -- Does not currently pass
--> master.lua <--
MASTER = {}
require 'simple'
require 'multi'
require 'shared1'
require 'shared2'
require 'shared3'
require 'reference'
--> simple.lua <--
MASTER.Simple = {}
function MASTER:simple() end
--> multi.lua <--
MASTER.Multi1 = {}
MASTER.Multi2 = {}
--> shared1.lua <--
MASTER.Shared = {}
--> shared2.lua <--
function MASTER.Shared:go1() end
--> shared3.lua <--
function MASTER.Shared:go2() end
--> reference.lua <--
function MASTER.Simple:ref1() return MASTER.Multi1 end
function MASTER.Simple:ref2() MASTER:simple() end
失敗:環境の設定
自己参照を使用して環境をマスターテーブルに設定することで、問題を解決しようと考えました。require
ただし、次のような関数を呼び出すと、環境が元に戻るため、これは機能しません。
--> master.lua <--
foo = "original"
local MASTER = setmetatable({foo="captured"},{__index=_G})
MASTER.MASTER = MASTER
setfenv(1,MASTER)
require 'simple'
--> simple.lua <--
print(foo) --> "original"
MASTER.Simple = {} --> attempt to index global 'MASTER' (a nil value)