在不影响全局命名空间的情况下创建一套相互依赖的Lua文件
tl;dr:什么设计模式允许您将Lua代码拆分到多个需要共享某些信息的文件上,而不影响全局表 背景 在Lua中创建库被认为是一种不好的形式,因为需要该库会影响全局命名空间:在不影响全局命名空间的情况下创建一套相互依赖的Lua文件,lua,Lua,tl;dr:什么设计模式允许您将Lua代码拆分到多个需要共享某些信息的文件上,而不影响全局表 背景 在Lua中创建库被认为是一种不好的形式,因为需要该库会影响全局命名空间: -->somelib.lua usercode.lua somelib.lua usercode.lua test\u用法.lua master.lua simple.lua multi.lua shared1.lua shared2.lua shared3.lua reference.lua master.lua simp
-->somelib.lua usercode.lua somelib.lua usercode.lua test\u用法.lua master.lua simple.lua multi.lua shared1.lua shared2.lua shared3.lua reference.lua master.lua simple.lua“原始”
MASTER.Simple={}-->尝试索引全局“MASTER”(一个空值)
我们可以通过更改主文件来修改运行所有必需代码的环境来解决此问题:
-->master.lua问题涉及:
制作模块时不污染全球空间
以这样的方式制作模块:出于维护等原因,模块可能被拆分为多个文件
我对上述问题的解决方案在于调整Lua中的“returnastable”习惯用法,以便在需要在子模块之间传递状态时,返回一个返回表的函数,而不是返回表
这适用于完全依赖于某个根模块的子模块。如果它们是独立加载的,那么它们要求用户知道在使用模块之前需要调用该模块。这与其他模块不同,其他模块都有一组方法,可以从local a=require('a')
开始
无论如何,它是这样工作的:
--callbacks.lua a -- sub-module
return function(self)
local callbacks = {}
callbacks.StartElement = function(parser, elementName, attributes)
local res = {}
local stack = self.stack
---awesome stuff for about 150 lines...
return callbacks
end
要使用它,你可以
local make_callbacks = require'callbacks'
self.callbacks = make_callbacks(self)
或者,更好的做法是,在将回调表分配给父模块时,只需调用require的返回值,如下所示:
self.callbacks = require'trms.xml.callbacks'(self)
大多数时候,我尽量不这样做。如果我在子模块之间传递状态或self,我发现我经常做错事。我的内部政策是,如果我正在做与另一个文件高度相关的事情,我可能会没事。更有可能的是,我把一些东西放错了位置,有一种方法可以做到这一点,而不需要在模块之间传递任何东西
我不喜欢这样做的原因是,我通过表传递的文件中有我正在使用的方法和属性。我不能自由地重构我的一个文件的内部实现,而不影响其他文件。因此,我谦虚地建议,这个成语是一面黄旗,但可能不是一面红旗
虽然这解决了没有全局变量的状态共享问题,但它并不能真正保护用户避免意外遗漏local
。如果我可以回答这个隐含的问题
我要做的第一件事是从我的模块中删除对全局环境的访问。记住,只有在我不知道的情况下,它才可用
重置\u ENV
,重置它是我做的第一件事。这是通过将所需内容打包到新的\u ENV
表中来实现的
_ENV = {print = print,
pairs = pairs, --etc
}
然而,不断地将lua中需要的所有内容重新输入到每个文件中是一个巨大的、容易出错的痛苦。为了避免这种情况,我在模块的基本目录中创建了一个文件,并将其用作所有模块和子模块公共环境的主目录。我称之为\u ENV.lua
注意:我不能为此使用“init.lua”或任何其他根模块,因为我需要能够从正在加载的子模块加载它
根模块,加载子模块,这些子模块是
我的缩写\u ENV.lua
文件如下所示:
--_ENV.lua
_ENV = {
type = type, pairs = pairs, ipairs = ipairs, next = next, print =
print, require = require, io = io, table = table, string = string,
lxp = require"lxp", lfs = require"lfs",
socket = require("socket"), lpeg = require'lpeg', --etc..
}
return _ENV
有了这个文件,我现在有了一个共同的基础。
我的所有其他模块都首先使用以下命令加载此文件:
_ENV = require'root_mod._ENV' --where root_mod is the base of my module.
这个设施对我来说至关重要,原因有二。首先,它让我
在全球空间之外。如果我发现我在全球环境中遗漏了一些东西(我花了很长时间才发现我没有
我可以回到我的\u ENV.lua
文件并添加它。作为
这是一个必需的文件,只加载一次,因此应用它
我所有的子模块都是0卡路里
其次,我发现它为我提供了我真正需要的一切
“返回模块为表”协议,只有少数例外情况需要“返回返回表的函数”。我有一个系统的方法来解决这个问题。我已经在Git存储库中重构了您的模块,向您展示了它是如何工作的:
其思想是让子部分返回一个函数,该函数将主模块作为参数
如果您通过打开master.lua中的源文件、附加页眉和页脚并使用loadstring
进行欺骗,您甚至可以不经修改地使用它们(只需修改master.lua,但它更复杂)。就我个人而言,我更喜欢保持它的明确性,这就是我在这里所做的。我不喜欢魔术:)
编辑:它与Andrew Stark的第一个解决方案非常接近,只是我直接在子模块中修补了主表。优点是您可以一次定义多个内容,如simple.lua、multi.lua和reference.lua文件。您赋予master.lua两项职责:
它定义了公共模块表
它导入所有子模块
相反,您应该为(1)创建一个单独的模块,并将其导入所有子模块:
--> common.lua <--
return {}
--> master.lua <--
require 'simple'
require 'multi'
require 'shared1'
require 'shared2'
require 'shared3'
require 'reference'
return require'common' -- return the common table
--> simple.lua <--
local MASTER = require'common' -- import the common table
MASTER.Simple = {}
function MASTER:simple() end
测试现在应该通过。TL;DR:不要返回
模块,尽早设置包。加载[…]=您的_模块
(仍然可以为空),然后只需在子模块中要求
模块,它将被正确共享
实现这一点的干净方法是显式注册模块,而不是依赖require
在最后隐式注册它。文件说:
加载给定的模块函数从查看
package.loaded
表以确定mod
--> test_usage.lua <--
local MASTER = require'master'
...
local _M = { } -- your module, however you define / name it
package.loaded[...] = _M -- recall: require calls loader( modname, something )
-- so `...` is `modname, something` which is shortened
-- to just `modname` because only one value is used