lua 類支持屬性不能被修改


背景

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM