现在的位置: 首页 > 综合 > 正文

[Lua基础]包package——模块开发

2019年01月09日 ⁄ 综合 ⁄ 共 3573字 ⁄ 字号 评论关闭
文章目录

包是一种组织代码的方式。很多语言专门提供了某种机制组织全局变量的命名,比如Modula的modules,Java和Perl的packages,C++的namespaces。每一种机制对在package中声明的元素的可见性以及其他一些细节的使用都有不同的规则。但是他们都提供了一种避免不同库中命名冲突的问题的机制每一个程序库创建自己的命名空间,在这个命名空间中定义的名字和其他命名空间中定义的名字互不干涉。

Lua并没有提供明确的机制来实现packages。然而,我们通过语言提供的基本的机制很容易实现他。主要的思想是:像标准库一样,使用表来描述package

使用表实现packages的明显的好处是:我们可以像其他表一样使用packages,并且可以使用语言提供的所有的功能,带来很多便利。大多数语言中,packages不是第一类值(first-class values)(也就是说,他们不能存储在变量里,不能作为函数参数。。。)因此,这些语言需要特殊的方法和技巧才能实现类似的功能。Lua中,虽然我们一直都用表来实现pachages,但也有其他不同的方法可以实现package.

vector3d = {}  -- 包名  
function vector3d.function1()  
......  
end  
function vector3d.function2()  
......  
      if (vector3d.function1()) then  
      ......  
      end  
end  
return vector3d 

这样定义的就是一个vector3d包,使用require语言打开这个包后,就可以使用 vector3d.function1vector3d.function2这两个函数了。

这是最直接最好理解的一种Package定义方式,但是有一定的弊端。这个弊端主要体现在Package的实现过程中。可以看到,即使在

vector3d.function2()中使用function1()函数,也必须完整的加上vector3d包名,否则无法进行函数调用Package的作者要稍微累一点,不过使用者倒是还好。特别的注意最后的 return vector3d 语句,有了这句后调用者可以按照如下方式重命名包:

MyPackage =  require "vector3d"  
MyPackage.function2() 

方式二使用局部函数定义所有的Package内函数,然后在Package的结尾处将需要公开的函数直接放入Package。代码看起来像这样:

vector3d = {}  -- 包名  
local function function1()  
......  
end  
 
local function function2()  
......  
      if (function1()) then  
      ......  
      end  
end  
vector3d = {function1 = functoin1,   
function2function2 = function2  
}  
return vector3d 

最后给包中赋值的部分就是将需要的接口公开的部分。这样做的好处:不需要公开的函数可以完全隐藏起来(都是local函数)Package内部的各个函数相互之间调用的时候不再需要加Package名称进行区分可以按照需要随意的重命名Package公开的接口名称。这种方式的弊端在于定义的时候需要写上local,这算不算弊端就看你了 - - 对我个人而言,可以用local
n = {}来保存数据和定义私有变量和函数
。能明确的区分出接口和私有的定义,公开接口的名称还可以随意改变,这就意味着可以随意替换内部实现而不需要影响外部调用者。

无论用什么方法去定义Package,都是为了在逻辑上更好的规划代码层次。LUA中的table机制的确是一个活力无限的机制啊。Package依靠这个实现,LUA本身自己有些机制也依赖于Table(比如全局变量就放在_G表中)

如何"拆开"Package的代码段,很好的展现了table的强大之处(Package也是在table上构筑的逻辑产物)。将Package拆开的意思,就是将所有Package中公开的名字放入_G表中。也就是让 Package.A() 变成_G.A  (_G在一般情况下不需要写,默认引用了)

function openpackage (ns)  
      for n,v in pairs(ns)   
     do  
            _G[n] = v  
     end  
end 

实现方式

一般在一个Lua文件内以module函数开始定义一个包。module同时定义了一个新的包的函数环境,以使在此包中定义的全局变量都在这个环境中,而非使用包的函数的环境中。理解这一点非常关键。以前面的代码为例, “module(..., package.seeall)”的意思是定义一个包,包的名字与定义包的文件的名字相同(除去文件名后缀,在前面的代码中,就是“mypack”),并且在包的函数环境里可以访问使用包的函数环境(比如,包的实现使用了print,这个变量没有在包里定义,而是定义在使用包的外部环境中)。

使用方式

一般用require函数来导入一个包,要导入的包必须被置于包路径(packagepath)上。包路径可以通过package.path或者环境变量来设定。一般来说,当前工作路径总是在包路径中。

--testP.lua:
pack = require "mypack" --导入包
print(ver or "No ver defined!")
print(pack.ver)
print(aFunInMyPack or
"No aFunInMyPack defined!")
pack.aFunInMyPack()
print(aFuncFromMyPack or
"No aFuncFromMyPack defined!")
aFuncFromMyPack()

--mypack.lua:
module(..., package.seeall) --定义包
ver = "0.1 alpha"
function aFunInMyPack()
print("Hello!")
end
_G.aFuncFromMyPack =
aFunInMyPack

执行testP.lua的输出结果:
No ver defined!
0.1 alpha
No aFunInMyPack defined!
Hello!
function: 003CBFC0
Hello!

定义模块的方式

定义module有两种方式,旧的方式,适用于Lua 5.0以及早期的5.1版本,新的方式支持新发布的Lua5.1和5.2版本。

旧的方式

通过module("...", package.seeall)来显示声明一个包。看很多github上面早期的开源项目使用的都是这种方式,但官方不推荐再使用这种方式。

定义:

-- oldmodule.lua
module("oldmodule", package.seeall)
function foo()
  print("oldmodule.foo called")
end

使用:

require "oldmodule"
oldmodule.foo()
  • 1.module() 第一个参数就是模块名,如果不设置,缺省使用文件名。
  • 2.第二个参数package.seeall,默认在定义了一个module()之后,前面定义的全局变量就都不可用了,包括print函数等,如果要让之前的全局变量可见,必须在定义module的时候加上参数package.seeall。 具体参考云风这篇文章
  • package.seeall(module)功能:为module设置一个元表,此元表的__index字段的值为全局环境_G。所以module可以访问全局环境.

之所以不再推荐module("...", package.seeall)这种方式,官方给出了两个原因。

  • 1.package.seeall这种方式破坏了模块的高内聚,原本引入oldmodule只想调用它的foo()函数,但是它却可以读写全局属性,例如oldmodule.os.
  • 2.第二个缺陷是module函数的side-effect引起的,它会污染全局环境变量。module("hello.world")会创建一个hello的table,并将这个table注入全局环境变量中,这样使得不想引用它的模块也能调用hello模块的方法。

新的方式

通过return table来实现一个模块

--newmodule.lua
local newmodule = {}
function newmodule.foo()
  print("newmodule.foo called")
end
return newmodule

使用

local new = require "newmodule"
new.foo()

因为没有了全局变量和module关键字,引用的时候必须把模块指定给一个变量

【上篇】
【下篇】

抱歉!评论已关闭.