Lua Readonly Table
环境是Lua5.3。
需求
当table已存在的key/value被修改时,报错提示。当table新增的key/value时,报错提示。其他时候,能够像普通的table一样使用。
简单实现
建一个空的table,设置元方法__index,读数据时从原来的table读。再设置__newindex元方法,方法内调用error函数。代码如下:
1 | local metatable = {} |
调用read_only函数后,我们得到了一个新的table。我们去访问原来的data中的key时,由于retValue没有这个索引,就会触发__index元方法调用。当我们尝试修改retValue的某个值,因为retValue没有对应的索引,所以会触发__newindex元方法。这里发现我们retValue里有____ro_raw_table这个变量,我主要是想用这个变量来判断表是不是一个只读表。我们可以对read_only做个优化,如果一个表已经是只读的了,直接返回就行了,避免套娃行为。
简单优化
优化一下read_only,代码如下:
1 | function read_only(data) |
__index也可以优化一下,同时对返回值做一下处理。代码如下:
1 | function metatable.__index(ro_table, key) |
还可以加一个read_only_cast方法,用于将只读表强转成非只读的。项目里可能用得上。代码如下:
1 | function read_only_cast(data) |
一切都很顺利,现在我们可以用pairs和ipairs试试遍历只读表。发现问题了。pairs只能遍历出____ro_raw_table这一个元素。并不能把原来表中的键值对遍历出来。ipairs倒是没有问题,遍历出来的就是原表中的数据。原因是ipairs遍历会固定从索引1开始访问,所以调用__index元方法,因此结果是正确的。pairs遍历时会调用__pairs元方法,如果没有,就会调用全局的next函数来完成遍历。next方法内部并不会调用任何元方法,所以会遍历不到数据。知道原因后,我们继续实现__pairs元方法。
实现pairs
__pairs元方法需要返回3个参数,第一个是遍历时使用的next方法,我们需要自定义一个。第二个和第三个参数是第一次调用next方法时传的两个参数。第二个我们传____ro_raw_table就好。第三个我们传nil,表示从头开始遍历。实际使用时,我们需要定义一个闭包函数,用一个局部变量来跟踪上一次遍历的index。代码如下:
1 | function metatable.__pairs(ro_table) |
next方法需要两个参数,第一个是table,第二个是index。返回两个参数,第一个是nextKey,第二个是nextValue。如果要用next来遍历table,index为nil时,代表从头遍历。下一次调用next需要将上一次返回的nextKey作为index传进去,这一步是pairs自动完成的。我们自己定义的read_only_next也需要遵守这个规则。这里发现我没有写第二个参数,因为我对nextKey做了read_only处理,所以pairs自动传过来的index是不正确的,我干脆就不写了。我们需要用闭包变量来记录真实的nextKey。
让只读表更自然
核心问题已经在上边都解决了。这一段就是添加一些边边角角的东西。根据自己的需要来。
实现__len,支持#操作。代码如下:
1 | function metatable.__len(ro_table) |
实现__tostring,字符串打印时能加上readonly前缀。代码如下:
1 | function metatable.__tostring(ro_table) |
实现__eq,项目内有判等的操作,需要支持一下。代码如下:
1 | function metatable.__eq(left, right) |
如果原始table的元表也实现过__eq方法,也需要修改下。因为lua优先使用左边变量的__eq方法。
贴一下github地址:readonly.lua
临走前别忘了点赞哦。