首页 > 代码库 > lua 类支持属性不能被修改

lua 类支持属性不能被修改

背景

lua是类是借助表的来实现的, 类被定义后, 在使用场景下, 不希望被修改。如果被修改, 则影响的类的原始定义, 影响所有使用类的地方。

例如:

--- router.lua class filerouter = class()router.xxx = function xxx end--- app.luarouter.xxx = function yyy end

 

故提出新的要求:

1、 对于类在应用场景下,不能修改属性。

2、 对于类在应用场景下, 不能添加新的属性。

 

类的实现代码:

local _M = {}-- Instantiates a classlocal 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 instend-- 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 clonefunction _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 falseendreturn _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)endprint(tab.bb)tab[1] = 1tab["aa"] = 1tab["bb"] = 1tab["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 classlocal 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 instend-- 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 clonefunction _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 falseendreturn _M

 

app

local oopclass = require("oopclass")local class = oopclass.classlocal instanceof = oopclass.instanceoflocal superTab =  class()superTab.test = function ( self )    print("superTab test")endsuperTab:freeze()superTab.test2 = function ( self )    print("superTab test2")endlocal 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

 

lua 类支持属性不能被修改