背景
lua是類是借助表的來實現的, 類被定義后, 在使用場景下, 不希望被修改。如果被修改, 則影響的類的原始定義, 影響所有使用類的地方。
例如:
--- router.lua class file router = class() router.xxx = function xxx end --- app.lua router.xxx = function yyy end
故提出新的要求:
1、 對於類在應用場景下,不能修改屬性。
2、 對於類在應用場景下, 不能添加新的屬性。
類的實現代碼:
local _M = {} -- Instantiates a class local function _instantiate(class, ...) -- 抽象類不能實例化 if rawget(class, "__abstract") then error("asbtract class cannot be instantiated.") end -- 單例模式,如果實例已經生成,則直接返回 if rawget(class, "__singleton") then -- _G[class]值為本class的實例 if _G[class] then return _G[class] end end local inst = setmetatable({__class=class}, {__index = class}) if inst.__init__ then inst:__init__(...) end --單例模式,如果實例未生成,則將實例記錄到類中 if rawget(class, "__singleton") then if not _G[class] then _G[class] = inst -- 對類對象增加實例獲取接口 class.getInstance = function ( self ) return _G[class] end -- 銷毀單例,為后續建立新單例准備 class.destroyInstance = function ( self ) _G[class] = nil end end end return inst end -- LUA類構造函數 function _M.class(base) local metatable = { __call = _instantiate, __index = base } -- __parent 屬性緩存父類,便於子類索引父類方法 local _class = {__parent = base} -- 在class對象中記錄 metatable ,以便重載 metatable.__index _class.__metatable = metatable return setmetatable(_class, metatable) end --- Test whether the given object is an instance of the given class. -- @param object Object instance -- @param class Class object to test against -- @return Boolean indicating whether the object is an instance -- @see class -- @see clone function _M.instanceof(object, class) local meta = getmetatable(object) while meta and meta.__index do if meta.__index == class then return true end meta = getmetatable(meta.__index) end return false end return _M
他山之石
http://www.lua.org/pil/13.4.5.html
function readOnly (t) local proxy = {} local mt = { -- create metatable __index = t, __newindex = function (t,k,v) error("attempt to update a read-only table", 2) end } setmetatable(proxy, mt) return proxy end
應用
days = readOnly{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"} print(days[1]) --> Sunday days[2] = "Noday" stdin:1: attempt to update a read-only table
評價:
此方法雖然實現的 新需求兩則, 但是不能夠, 其借助有新增一級metatable, 破壞了現有的 原型鏈表繼承結構。
__index & __newindex
local tab = {1, 6 , aa="bb"} setmetatable(tab, {__index={bb="cc"}, __newindex = function (t,k,v) error("attempt to update a read-only table", 2) end}) for k,v in pairs(tab) do print(k, v) end print(tab.bb) tab[1] = 1 tab["aa"] = 1 tab["bb"] = 1 tab["aabbb"] = 1
LOG
>lua -e "io.stdout:setvbuf 'no'" "luatest.lua"
1 1
2 6
aa bb
cc
lua: luatest.lua:110: attempt to update a read-only table
stack traceback:
[C]: in function 'error'
luatest.lua:97: in function <luatest.lua:96>
luatest.lua:110: in main chunk
[C]: ?
>Exit code: 1
__index 釋義
http://www.lua.org/pil/13.4.1.html
I said earlier that, when we access an absent field in a table, the result is nil. This is true, but it is not the whole truth. Actually, such access triggers the interpreter to look for an
__index
metamethod: If there is no such method, as usually happens, then the access results in nil; otherwise, the metamethod will provide the result.
Window.mt.__index = function (table, key) return Window.prototype[key] end
Window.mt.__index = Window.prototype
__newindex 釋義
http://www.lua.org/pil/13.4.2.html
The
__newindex
metamethod does for table updates what__index
does for table accesses. When you assign a value to an absent index in a table, the interpreter looks for a__newindex
metamethod: If there is one, the interpreter calls it instead of making the assignment. Like__index
, if the metamethod is a table, the interpreter does the assignment in that table, instead of in the original one.
這兩個元表中的屬性, 首先是以元方法的形式出現的, 然后做了易用性接口適配, 可以是表。
不引入metatable情況下,實現類屬性只讀
在類表中引入 __prototype 表, 專門用於存儲 建立的 類field
__index 從__prototype中查找
__newindex 設置field到 __prototype 表中
oopclass
local _M = {} -- Instantiates a class local function _instantiate(class, ...) -- 抽象類不能實例化 if rawget(class, "__abstract") then error("asbtract class cannot be instantiated.") end -- 單例模式,如果實例已經生成,則直接返回 if rawget(class, "__singleton") then -- _G[class]值為本class的實例 if _G[class] then return _G[class] end end local inst = setmetatable({__class=class}, {__index=class}) if inst.__init__ then inst:__init__(...) end --單例模式,如果實例未生成,則將實例記錄到類中 if rawget(class, "__singleton") then if not _G[class] then _G[class] = inst -- 對類對象增加實例獲取接口 class.getInstance = function ( self ) return _G[class] end -- 銷毀單例,為后續建立新單例准備 class.destroyInstance = function ( self ) _G[class] = nil end end end return inst end -- LUA類構造函數 function _M.class(base) local metatable = { __call = _instantiate, } -- 先查原型表,然后查父親類 metatable.__index=function(t, k) local v = t.__prototype[k] if v then return v end local parent = t.__parent if parent then return parent[k] end return nil end -- 緩存類的field metatable.__newindex=function (t,k,v) rawset(t.__prototype, k, v) end local _class = {} -- __parent 屬性緩存父類 _class.__parent = base or {} -- 存儲此類的所有field _class.__prototype = {} -- 在class對象中記錄 metatable ,以便重載 metatable.__index _class.__metatable = metatable -- 將類冷凍,不允許新建刪除修改 _class.freeze = function ( self ) local mt = getmetatable(self) mt.__newindex=function (t,k,v) error("class is frozen, cannot revise") end end return setmetatable(_class, metatable) end --- Test whether the given object is an instance of the given class. -- @param object Object instance -- @param class Class object to test against -- @return Boolean indicating whether the object is an instance -- @see class -- @see clone function _M.instanceof(object, class) local objClass = object.__class if not objClass then return false end while objClass do if objClass == class then return true end objClass = objClass.__parent end return false end return _M
app
local oopclass = require("oopclass") local class = oopclass.class local instanceof = oopclass.instanceof local superTab = class() superTab.test = function ( self ) print("superTab test") end superTab:freeze() superTab.test2 = function ( self ) print("superTab test2") end local tab = class(superTab) local tabObj = tab() tabObj:test() print( instanceof(tabObj, tab) ) print( instanceof(tabObj, superTab) )
LOG:
>lua -e "io.stdout:setvbuf 'no'" "luatest.lua"
lua: .\oopclass.lua:85: class is frozen, cannot revise
stack traceback:
[C]: in function 'error'
.\oopclass.lua:85: in function <.\oopclass.lua:84>
luatest.lua:17: in main chunk
[C]: ?
>Exit code: 1
去掉freeze語句 superTab:freeze()
LOG:
>lua -e "io.stdout:setvbuf 'no'" "luatest.lua"
superTab test
true
true
>Exit code: 0