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

时间: 2024-08-06 04:37:46

lua 类支持属性不能被修改的相关文章

类:属性方法的私有公有详解

1 # -*- coding: UTF-8 -*- 2 # 无论名称是self还是cls,类方法或实例方法都是把第一个参数默认成cls或self,即调用类或调用实例才能使用. 3 4 class JustCounter: 5 publicCount = 0 # 公开属性 6 __secretCount = 0 # 私有属性 7 def count_secret(self): 8 print self.__secretCount 9 def count_public(self): 10 print

Selectivizr-让IE6~8支持CSS3伪类和属性选择器

一.下载和demo 下载: 您可以狠狠地点击这里:selectivizr-1.0.0.zip(右键-[目标|链接另存为] 9.24K) demo: 您可以狠狠地点击这里:Selectivizr部分属性测试demo 二.简介 selectivizr是一个JavaScript工具,使IE浏览器也可以支持CSS3伪类以及属性选择器,使用很简单,只要把js include到页面上,然后你就可以(- o -)~zZ补觉了. 优点在于: 1. 让老的IE浏览器支持19个CSS3伪类,2个伪元素,以及所有的属

XML之自动生成类,添加,修改,删除类的属性

1. class ClassHelperDemo { public static void Main() { #region 演示一:动态生成类. //生成一个类t. Type t = ClassHelper.BuildType("MyClass"); #endregion #region 演示二:动态添加属性到类. //先定义两个属性. List<ClassHelper.CustPropertyInfo> lcpi = new List<ClassHelper.Cu

面向对象【day07】:类的属性-继承-经典类

本节内容 类的公有属性 析构函数 类的继承 新式类和经典类 一.类的公有属性 一.概述 前面我们讲了类的私有属性,现在我们来说说类的公有属性,这边很容易被人弄混淆,有人觉的,在__init__()构造方法中,除了私有属性,其他的都是公有属性了,其实这是一个错误的结论,并不是定义在__init__()初始化方法中的属性是公有属性(除私有属性),那什么是公有属性呢?揭起了大家的好奇心. 定义:指的是所属这个类的所有对象,都可以访问的属性,叫做公有属性. 二.公有属性 2.1 定义 说明:在类中直接定

ibernate学习笔记5---实体类或属性名与数据库关键字冲突、hql命名参数、hql实现通用分页

一.实体类或属性名与数据库关键字冲突问题1.实体类名与数据库中的关键字冲突比如:实体表User与oracle中的系统表冲突解决方式1:在xml中添加table属性,指定表名,使其不与name默认相等 [html] view plaincopyprint? <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hiber

springmvc+easyui datagrid columns的field支持属性的子属性(field.sonfield形式或者格式化程序形式)

所谓为了支持某属性的子属性,主要为了解决:在服务器返回的json格式的数据的某个属性带有子属性,而我们恰恰又需要使用到该子属性作为我们的datagrid的某个字段的.默认情况下datagrid只能支持一级属性字段(属性的属性字段属于二级字段). 对于这个问题的解决方案有两种方式: 1.就是更改esayui源文件,使其支持field.sonfield的形式. javascript语法为我们提供了两种方式获取一个对象的属性:点字符连接和[]方式.使用[]可以很方便的将一个属性通过字符串的方式获取.但

自订类的属性与字段如何枚举并提供控件作为系结来源?

我在" 数组详论"与"Enum详论"两篇文章中曾经介绍过如何将数组以及 Enum 项目当作系结控件的数据来源.然而,无论是数组或是 Enum 项目, 它们都有设定和使用上的巨大限制.其中, Enum 的每个项目都必须是常值, 换句话说, 项目的值无法动能控制.而数组的值虽然可以动态变更, 但无法提供设计时的 Intellisense 支持... 我在" 数组详论"与"Enum详论"两篇文章中曾经介绍过如何将数组以及 Enum

oc学习之对象作为类的属性

对象做为类的属性 1. 一个Target中的类无法直接在另外1个Target中访问. 2. 类的属性代表什么? a. 类的属性代表这类事物具有的共同的特征 b. 类的属性代表这个类所拥有的东西. 灯: 属性: 形状 功率 价格  品牌 剩余寿命 方法: 照明. 音响: 方法: 发声音. 门: 属性: 尺寸 材料 颜色 价格 方法: 开 关 3. 类的方法代表什么? a. 代表这个类所具备的行为. 这个类所具备的功能. b. 一个类有什么方法 就去分析这个类有什么功能. 4. 属性的本质是变量.

iOS开发&gt;学无止境 - 遍历Model类的属性并完善使用Runtime给Model类赋值

在前几天的一篇博客<iOS开发之使用Runtime给Model类赋值>中介绍了如何使用运行时在实体类的基类中添加给实体类的属性赋值的方法,这个方法的前提是字典的Key必须和实体类的Property Name相同,然后通过运行时来生成和执行Setter方法给Model类的属性赋值. 通 过Runtime来给Model类属性赋值的好处是多多的,它便于代码的后期维护,并且提高了开发效率.当你拿到解析后的字典时你不用一个一个的通过 key去把字典的值赋值给相应的Model类的属性,本篇博客中会给出如何