Q:Lua如何管理”package”?
A:Lua使用”table”来表示”package”,就像Lua标准库的做法一样。我们也可以使用”table”来创建自己的”package”。
-- "complex.lua"文件中,一个实现对复数运算的"package"。
complex = {}
-- 用一个"table"表示一个复数,"r"是实部,"i"是虚部。
function complex.new (r, i) return {r=r, i=i} end
-- defines a constant 'i'
complex.i = complex.new(0, 1)
function complex.add (c1, c2)
return complex.new(c1.r + c2.r, c1.i + c2.i)
end
function complex.sub (c1, c2)
return complex.new(c1.r - c2.r, c1.i - c2.i)
end
function complex.mul (c1, c2)
return complex.new(c1.r*c2.r - c1.i*c2.i,
c1.r*c2.i + c1.i*c2.r)
end
function complex.div (c1, c2)
local n = c2.r^2 + c2.i^2
return complex.new(
(c1.r * c2.r + c1.i * c2.i) / n,
(c1.i * c2.r - c1.r * c2.i) / n
)
end
--[[ 这个"return"不是必需的,一般加载"package"使用"require",
同时"package"的名字已经被放在全局变量中了。
但在"package"的最后返回自己是个好习惯,
它允许你用除了"require"以外的方式使用"package",比如"dofile()"。]]
return complex
-- "test.lua"文件中。
-- "require"方式。
require "complex"
c = complex.add(complex.i, complex.new(10, 20))
for i,v in pairs(c)
do
io.write(string.format("%s = %d, ", i, v)) --> r = 10, i = 21,
end
-- "dofile()"方式。
p = dofile("complex.lua")
c = p.add(p.i, p.new(10, 20))
for i,v in pairs(c)
do
io.write(string.format("%s = %d, ", i, v))
end
io.write("\n")
Q:如何将”package”中的方法设置为私有的?
A:将方法设置为local
。我们定义一个检查参数的私有方法,
-- "complex.lua"文件中。
complex = {}
-- 私有方法"checkComplex()",外部无法调用。
local function checkComplex (c)
if not ((type(c) == "table") and
tonumber(c.r) and tonumber(c.i)) then
error("bad complex number", 3)
end
end
function complex.add (c1, c2)
checkComplex(c1);
checkComplex(c2);
return P.new(c1.r + c2.r, c1.i + c2.i)
end
-- 其他相同的方法不列举。
...
可以注意到,私有方法定义与调用都不需要complex
前缀,而公有方法需要。书写complex
前缀实在是太麻烦了,同时我们将公有方法转私有,或者私有方法转公有时都需要注意是否需要书写这个前缀。
这里有个方法解决这些问题,可以将”package”内部的所有函数定义为local
,然后在最后将需要导出的函数放入全局表中导出,
-- "complex.lua"文件中。
complex = {}
-- 以下的所有方法在定义时都去掉了前缀,并且均声明为私有方法("local")。
local function new (r, i) return {r=r, i=i} end
local function checkComplex (c)
if not ((type(c) == "table") and
tonumber(c.r) and tonumber(c.i)) then
error("bad complex number", 3)
end
end
i = new(0, 1)
local function add (c1, c2)
checkComplex(c1);
checkComplex(c2);
return new(c1.r + c2.r, c1.i + c2.i)
end
local function sub (c1, c2)
return new(c1.r - c2.r, c1.i - c2.i)
end
local function mul (c1, c2)
return new(c1.r*c2.r - c1.i*c2.i,
c1.r*c2.i + c1.i*c2.r)
end
function div (c1, c2)
local n = c2.r^2 + c2.i^2
return new(
(c1.r * c2.r + c1.i * c2.i) / n,
(c1.i * c2.r - c1.r * c2.i) / n
)
end
-- 公有的方法才放在"complex"全局表中导出。
complex = {
new = new,
add = add,
sub = sub,
mul = mul,
div = div,
i = i,
}
return complex
现在,”package”中的方法终于不再需要书写前缀complex
了,在”package”内部无论调用公有方法还是私有方法都是相同的方式。而且修改方法的公有性或私有性也很简单,直接修改全局complex
表中的内容就好了。
附加:
1、”如何将’package’中的方法设置为私有的?”的”Q & A”中优化了”package”中方法的定义方式,使得无论是公有还是私有方法都摆脱了”package”名字前缀。不过与此同时,每个方法都需要显式定义为local
类型的,这很容易造成错误。一旦不小心漏写,就又将方法定义为全局的了。
解决这个问题,可以将”package”定义在独立的环境中,
-- "complex.lua"文件中。
complex = {}
-- 原先环境中的函数或变量需要能被访问,比如下面的"type()"。
setmetatable(complex, {__index = _ENV})
-- 下面的所有函数都会定义在独立的"complex"环境中。
_ENV = complex
-- 因为在独立的环境中,函数均定义成全局的也无所谓了。
function new (r, i) return {r=r, i=i} end
function checkComplex (c)
if not ((type(c) == "table") and
tonumber(c.r) and tonumber(c.i)) then
error("bad complex number", 3)
end
end
i = new(0, 1)
function add (c1, c2)
checkComplex(c1);
checkComplex(c2);
return new(c1.r + c2.r, c1.i + c2.i)
end
function sub (c1, c2)
return new(c1.r - c2.r, c1.i - c2.i)
end
function mul (c1, c2)
return new(c1.r*c2.r - c1.i*c2.i,
c1.r*c2.i + c1.i*c2.r)
end
function div (c1, c2)
n = c2.r^2 + c2.i^2
return new(
(c1.r * c2.r + c1.i * c2.i) / n,
(c1.i * c2.r - c1.r * c2.i) / n
)
end
return complex
这种方式有一个有趣的副作用,例如你为了安全性,屏蔽了_ENV
中的io
库(io.open()
,io.read()
,io.write()
等),但别人可以通过你提供的”package”访问你屏蔽的函数(例如complex.io.write()
等)。
2、因为”package”本身就是”table”,所以我们可以在”package”中内嵌”package”。