chapter 13_4 跟踪table的访问

  __index和__newindex都是在table中没有所需访问的index时才发挥作用。

因此,只有将一个table保持为空,才有可能捕捉到所有对它的访问。为了监视一个table的所有访问,就应该为真正的table创建一个代理。

这个代理就是一个空的table,其中__index和__newindex元方法可用于跟踪所有的访问,并将访问重定向到原来的table上。

假设我们要跟踪table t 的访问,可以这样做:

t  = {}          --original table, 在其他地方创建的
local _t = t     --保持对原table的一个私有访问
t = {}           --创建一个代理
local mt = {
            __index = function (t,k)
                        print("*access to elemet " .. tostring(k))
                        return _t[k]      --访问原table中的k
                    end
            __newindex = function(t,k,v)
                        print("*update of element" .. tostring(k) .. " to " .. tostring(v))
                        _t[k] = v        --更新原table的值
                    end
}
setmetatable(t,mt)    --将mt设置为 t 的元表

这段代码跟踪了所有对 table t 的访问:

t[2] = "hello"              -- *update of element 2 to hello
print(t[2])       --> hello        -- *access to element 2

但是上面的例子有一个问题,就是无法遍历原来的table。函数pairs只能操作代理table,而无法访问原来的table。

可以通过定义__pairs去遍历:

mt.__pairs = function()
    return function(_,k)
        return next(_t,k)
        end
end

  如果想要同时监视几个table,无须为每个table创建不同的元表。相反,只要以某种形式将每个代理与其原table关联起来,

并且所有代理都共享一个公共的元表。这个问题与前一节所讨论的将table与其默认值相关联的问题类似。

例如将原来table保存在代理table的一个特殊字段中,如下:

local index = {}        --创建私有索引
local mt = {            --创建元表
    __index = function(t,k)
            print("*access to element " .. tostring(k)
            return t[index][k]    --访问原来的table
        end
    __newindex = function(t,k,v)
            print("*update of element " .. tostring(k) .. " to " .. tostring(v))
            t[index][k] = v        --更新原来的table
        end
    __pairs = function(t)
            return function(t,k)
                return next(t[index],k)
            end , t
        end
}

function track(t)
    local proxy= {}
    proxy[index] = t
    setmetatable(proxy,mt)
    return proxy
end

现在,若要监视table t ,唯一要做的就是执行:

t = track(t)

只读的table

  通过代理的概念,可以很容易地就实现出只读的table,只需跟踪所有对table的跟新操作,并引发一个错误就可以了。

由于无须跟踪查询访问,所以对于__index元方法可以直接使用原table来代替函数。这也更简单,并且在重定向所有查询到原table时效率也更高。

不过,这种做法要求为每个只读代理创建一个新的元表,其中__index指向原来的table。

function readOnly(t)
    local proxy = {}
    local mt = {
        __index =t ,    
        __newindex = function(t,k,v)
                        error("*attempt to update a read-only table",2)
                    end
    }
    setmetatable(proxy,mt)
    return proxy
end

下面是一个使用的示例,创建了一个表示星期的只读table:

days = readOnly{"Sunday","Monday","Tuesday","Thursday","Friday","Saturday"}
print(days[1])        -->Sunday
days[2] = "Noday"     -- > stdin:1: attempt to update a read-only table

以上内容来自:《Lua程序设计第二版》和《Programming in Lua  third edition 》

 

posted @ 2016-09-08 11:05  daiker  阅读(208)  评论(0编辑  收藏  举报