包是一種組織代碼的方式。很多語言專門提供了某種機制組織全域變數的命名,比如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.function1和vector3d.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 alphaNo aFunInMyPack defined!Hello!function: 003CBFC0Hello!
定義模組的方式
定義module有兩種方式,舊的方式,適用於Lua 5.0以及早期的5.1版本,新的方式支援新發布的Lua5.1和5.2版本。
舊的方式 通過module("...", package.seeall)來顯示聲明一個包。看很多github上面早期的開源項目使用的都是這種方式,但官方不推薦再使用這種方式。
定義:
-- oldmodule.luamodule("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.lualocal newmodule = {}function newmodule.foo() print("newmodule.foo called")endreturn newmodule
使用
local new = require "newmodule"new.foo()
因為沒有了全域變數和module關鍵字,引用的時候必須把模組指定給一個變數。