• 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

  • 相关阅读:
    python3 内置函数
    python3 模块和包
    python3 面向对象编程
    vue.js的devtools安装
    数组对象排序
    插件资源库
    Vue.js 2.0生命周期
    vue学习--自定义全局vue组件
    搭建VUE项目
    入职新公司
  • 原文地址:https://www.cnblogs.com/lightsong/p/5906593.html
Copyright © 2020-2023  润新知