15.4 使用全局表

上面这些创建package的方法的缺点是:他们要求程序员注意很多东西,比如,在声明的时候也很容易忘掉local关键字。全局变量表的Metamethods提供了一些有趣的技术,也可以用来实现package。这些技术中共同之处在于:package使用独占的环境。这很容易实现:如果我们改变了packagechunk的环境,那么由package创建的所有函数都共享这个新的环境。

最简单的技术实现。一旦package有一个独占的环境,不仅所有她的函数共享环境,而且它的所有全局变量也共享这个环境。所以,我们可以将所有的公有函数声明为全局变量,然后他们会自动作为独立的表(表指package的名字)存在,所有package必须要做的是将这个表注册为package的名字。下面这段代码阐述了复数库使用这种技术的结果:

local P = {}

complex = P

setfenv(1, P)

现在,当我们声明函数add,她会自动变成 complex.add:

function add (c1, c2)

    return new(c1.r + c2.r, c1.i + c2.i)

end

另外,我们可以在这个package中不需要前缀调用其他的函数。例如,add函数调用new函数,环境会自动转换为complex.new。这种方法提供了对package很好的支持:程序员几乎不需要做什么额外的工作,调用同一个package内的函数不需要前缀,调用公有和私有函数也没什么区别。如果程序员忘记了local关键字,也不会污染全局命名空间,只不过使得私有函数变成公有函数而已。另外,我们可以将这种技术和前一节我们使用的package名的方法组合起来:

local P = {}      -- package

if _REQUIREDNAME == nil then

    complex = P

else

    _G[_REQUIREDNAME] = P

end

setfenv(1, P)

这样就不能访问其他的packages了。一旦我们将一个空表P作为我们的环境,我们就失去了访问所有以前的全局变量。下面有好几种方法可以解决这个问题,但都各有利弊。

最简单的解决方法是使用继承,像前面我们看到的一样:

local P = {}      -- package

setmetatable(P, {__index = _G})

setfenv(1, P)

(你必须在调用setfenv之前调用setmetatable,你能说出原因么?)使用这种结构,package就可以直接访问所有的全局标示符,但必须为每一个访问付出一小点代价。理论上来讲,这种解决方法带来一个有趣的结果:你的package现在包含了所有的全局变量。例如,使用你的package人也可以调用标准库的sin函数:complex.math.sin(x)。(Perl's package 系统也有这种特性)

另外一种快速的访问其他packages的方法是声明一个局部变量来保存老的环境:

local P = {}

pack = P

local _G = _G

setfenv(1, P)

现在,你必须对外部的访问加上前缀_G.,但是访问速度更快,因为这不涉及到metamethod。与继承不同的是这种方法,使得你可以访问老的环境;这种方法的好与坏是有争议的,但是有时候你可能需要这种灵活性。

一个更加正规的方法是:只把你需要的函数或者packages声明为local

local P = {}

pack = P

 

-- Import Section:

 

-- declare everything this package needs from outside

local sqrt = math.sqrt

local io = io

 

-- no more external access after this point

setfenv(1, P)

这一技术要求稍多,但他使你的package的独立性比较好。他的速度也比前面那几种方法快。


相关链接:
lua程序设计目录 - 中国lua开发者 - lua论坛