代理的思想--實現lua中table的跟蹤與只讀

table的跟蹤

本文跟蹤table,是指對一個table 的操作,比如訪問和更新進行跟蹤。當訪問一個 table 或者更新 table 中的某個元素時,lua 首先會在 table 查找是否存在該元素,如果沒有,就會查找 table 是否存在 __index(訪問) 或者 __newindex(更新) 原方法。以訪問爲例,首先在 table 中查找某個字段,如果不存在,解釋器會去查找 __index 這個原方法,如果仍然沒有,返回 nil。所以說,__index__newindex 是在 table 中沒有所需訪問的 index 時才發揮作用的。

根據上面這種思路,如果我們想跟蹤一個 table 的操作行爲,那麼需要一個空表,每次對這個空表操作的時候,就會使用 __index 或者 __newindex 這些元方法,在元方法中對原始 table 進行訪問和操作,並打印跟蹤信息。而之前創建的那個空表,就是代理。

對單個 table 的跟蹤

先創建一個代理表,爲空表,然後創建 metatable

local _t = orignal_table    -- 對原表的一個私有訪問,原表在其他地方創建的
local proxy = {}
local mt = {
    __index = function (proxy, k)
        print("access to element " .. tostring(k))
        return _t[k]    -- 這裏訪問的其實就是原表中的元素了
    end,
    __newindex = function (proxy, k, v)
        print("update of element " .. tostring(k) .. " to " .. tostring(v))
        _t[k] = v       -- 更新原表中的元素
    end
}

setmetatable(proxy, mt)

-- 操作,觀察結果
proxy[2] = "lua"
print(proxy[2])

結果爲:

update of element 2 to lua
access to element 2
lua

對多個table的跟蹤

對多個table的跟蹤,思路與上面相同,只需要按照某種形式將每個代理與原表 table 關聯起來,並且所有的代理都共享一個公共的元表 metatable,比如將原表 table 保存在代理 table 的一個特殊字段中

proxy[index] = t    -- t 就是原表 table

代碼如下所示:

local index = {}    --  創建私有索引,即原表在代理表中特殊字段

local mt = {
    __index = function (t, k)
        print("access to element " .. tostring(k))
        return t[index][k]
    end,
    __newindex = function (t, k, v)
        print("update of element " .. tostring(k) .. " to " .. tostring(v))
        t[index][k] = v
    end
}

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

-- ori_table = {} 在其他地方創建的原表,對他進行跟蹤
local _o = track(ori_table)

_o[2] = "lua"
print(_o[2])

觀察輸出結果,與上面的相同

只讀 table

只讀 table,通過上面的代理的思想也很容易實現,對元方法 __index ,訪問自身,對更新操作的元方法 __newindex,引發一個錯誤,不允許更新,如下所示:

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

因爲 proxy 代理 table 是一個空表,當訪問時,解釋器通過元方法 __index 訪問,這個元方法此處就是一個 table ,就是原表 table ,直接返回原表 table 中的字段。如果更新字段,解釋器將使用元方法 __newindex 進行更新,觸發一個錯誤,不允許更新,並說明這是隻讀的 table。

參考文獻:
1. 《Programming In Lua》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章