[Lua基礎]包package——模塊開發

抄至於:http://blog.csdn.net/shimazhuge/article/details/41351435



包是一種組織代碼的方式。很多語言專門提供了某種機制組織全局變量的命名,比如Modula的modules,Java和Perl的packages,C++的namespaces。每一種機制對在package中聲明的元素的可見性以及其他一些細節的使用都有不同的規則。但是他們都提供了一種避免不同庫中命名衝突的問題的機制每一個程序庫創建自己的命名空間,在這個命名空間中定義的名字和其他命名空間中定義的名字互不干涉。

Lua並沒有提供明確的機制來實現packages。然而,我們通過語言提供的基本的機制很容易實現他。主要的思想是:像標準庫一樣,使用表來描述package

使用表實現packages的明顯的好處是:我們可以像其他表一樣使用packages,並且可以使用語言提供的所有的功能,帶來很多便利。大多數語言中,packages不是第一類值(first-class values)(也就是說,他們不能存儲在變量裏,不能作爲函數參數。。。)因此,這些語言需要特殊的方法和技巧才能實現類似的功能。Lua中,雖然我們一直都用表來實現pachages,但也有其他不同的方法可以實現package.

[cpp] view plain copy
  1. vector3d = {}  -- 包名    
  2. function vector3d.function1()    
  3. ......    
  4. end    
  5. function vector3d.function2()    
  6. ......    
  7.       if (vector3d.function1()) then    
  8.       ......    
  9.       end    
  10. end    
  11. return vector3d   

這樣定義的就是一個vector3d包,使用require語言打開這個包後,就可以使用 vector3d.function1vector3d.function2這兩個函數了。

這是最直接最好理解的一種Package定義方式,但是有一定的弊端。這個弊端主要體現在Package的實現過程中。可以看到,即使在

vector3d.function2()中使用function1()函數,也必須完整的加上vector3d包名,否則無法進行函數調用Package的作者要稍微累一點,不過使用者倒是還好。特別的注意最後的 return vector3d 語句,有了這句後調用者可以按照如下方式重命名包:

[cpp] view plain copy
  1. MyPackage =  require "vector3d"    
  2. MyPackage.function2()   

方式二使用局部函數定義所有的Package內函數,然後在Package的結尾處將需要公開的函數直接放入Package。代碼看起來像這樣:

[cpp] view plain copy
  1. vector3d = {}  -- 包名    
  2. local function function1()    
  3. ......    
  4. end    
  5.    
  6. local function function2()    
  7. ......    
  8.       if (function1()) then    
  9.       ......    
  10.       end    
  11. end    
  12. vector3d = {function1 = functoin1,     
  13. function2function2 = function2    
  14. }    
  15. 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在一般情況下不需要寫,默認引用了)

[cpp] view plain copy
  1. function openpackage (ns)    
  2.       for n,v in pairs(ns)     
  3.      do    
  4.             _G[n] = v    
  5.      end    
  6. end   

實現方式

一般在一個Lua文件內以module函數開始定義一個包。module同時定義了一個新的包的函數環境,以使在此包中定義的全局變量都在這個環境中,而非使用包的函數的環境中。理解這一點非常關鍵。以前面的代碼爲例, “module(..., package.seeall)”的意思是定義一個包,包的名字與定義包的文件的名字相同(除去文件名後綴,在前面的代碼中,就是“mypack”),並且在包的函數環境裏可以訪問使用包的函數環境(比如,包的實現使用了print,這個變量沒有在包裏定義,而是定義在使用包的外部環境中)。

使用方式

一般用require函數來導入一個包,要導入的包必須被置於包路徑(packagepath)上。包路徑可以通過package.path或者環境變量來設定。一般來說,當前工作路徑總是在包路徑中。

[cpp] view plain copy
  1. --testP.lua:  
  2. pack = require "mypack" --導入包  
  3. print(ver or "No ver defined!")  
  4. print(pack.ver)  
  5. print(aFunInMyPack or  
  6. "No aFunInMyPack defined!")  
  7. pack.aFunInMyPack()  
  8. print(aFuncFromMyPack or  
  9. "No aFuncFromMyPack defined!")  
  10. aFuncFromMyPack()  
  11.   
  12. --mypack.lua:  
  13. module(..., package.seeall) --定義包  
  14. ver = "0.1 alpha"  
  15. function aFunInMyPack()  
  16. print("Hello!")  
  17. end  
  18. _G.aFuncFromMyPack =  
  19. aFunInMyPack  
  20.   
  21. 執行testP.lua的輸出結果:  
  22. No ver defined!  
  23. 0.1 alpha  
  24. No aFunInMyPack defined!  
  25. Hello!  
  26. function: 003CBFC0  
  27. Hello!  

定義模塊的方式

定義module有兩種方式,舊的方式,適用於Lua 5.0以及早期的5.1版本,新的方式支持新發布的Lua5.1和5.2版本。

舊的方式

通過module("...", package.seeall)來顯示聲明一個包。看很多github上面早期的開源項目使用的都是這種方式,但官方不推薦再使用這種方式。

定義:

[cpp] view plain copy
  1. -- oldmodule.lua  
  2. module("oldmodule", package.seeall)  
  3. function foo()  
  4.   print("oldmodule.foo called")  
  5. end  
使用:
[cpp] view plain copy
  1. require "oldmodule"  
  2. 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來實現一個模塊
[cpp] view plain copy
  1. --newmodule.lua  
  2. local newmodule = {}  
  3. function newmodule.foo()  
  4.   print("newmodule.foo called")  
  5. end  
  6. return newmodule  
使用
[cpp] view plain copy
  1. local new = require "newmodule"  
  2. new.foo()  
因爲沒有了全局變量和module關鍵字,引用的時候必須把模塊指定給一個變量


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章