基於代理伺服器的封裝

lua-users home
wiki

下列技巧可以用來透過代理伺服器表格達到物件封裝。目標是一個代理物件,它公開另一個物件的方法(表示),但保持實作隱藏。棘手的部分(因此有點昂貴)是找一個方法來儲存代理回它的表示的連結,且其他程式碼無法存取。弱金鑰表格看來像是選項之一,但半弱表格容易產生循環參照問題。元表格看來像是選項之一,但元表格對 Lua 程式碼來說不是完全隱藏,就是完全外露。(我忽略撰寫 C-based 實作的選項。這樣做會避免最後這個問題。)因此,這裡採用的解決方案是結合完全弱表格與元表格基礎的參照,才能建立起強連結。

local function noProxyNewIndex()
    error "Cannot set field in a proxy object"
end

function makeEncapsulator()
    -- Returns two tables: The first converts from representations to proxies. The
    -- second converts the other way. The first will auto-generate new proxies.

    local proxy2rep = setmetatable( {}, { __mode = "kv" } )

    local rep2proxy = {}
        -- This will be made weak later, but we need to construct more machinery
        
    local function genMethod( methods, k )
        -- Index function for the __index metatable entry
        
        local result = function( proxy, ... )
            local rep = proxy2rep[ proxy ]
            return rep[ k ]( rep, ... ) -- Lua 5.1!
        end
        
        methods[ k ] = result
        
        return result
    
    end

    local proxyIndex = setmetatable( {}, { __index = genMethod } )
        -- __index table for proxies
    
    local function makeProxy( rep )
    
        local proxyMeta = {
            __metatable = "< protected proxy metatable >",
            rep = rep, -- GC protection, we won't be able to read this
            __index = proxyIndex,
            __newindex = noProxyNewIndex
        }
    
        local proxy = setmetatable( {}, proxyMeta )
        
        proxy2rep[ proxy ] = rep
        rep2proxy[ rep ] = proxy
        
        return proxy
    
    end
    
    setmetatable( rep2proxy, {
        __mode = "kv",
        __metatable = "< protected >",
        __index = function( t, k )
            local proxy = makeProxy( k )
            t[ k ] = proxy
            return proxy
        end
    } )
    
    return rep2proxy, proxy2rep

end

使用方法如下。我們建立一個封裝器,然後再透過它執行需要封裝的物件。客戶端必須留心在封裝障礙時要封裝一個物件,因為在任何方法中,自我等於真實物件,而非代理伺服器。

local encapsulator = makeEncapsulator()

local foo = { hello =
  function(self) print("Hello from " .. tostring(self)) end }
print("foo = " .. tostring(foo))

local efoo = encapsulator[foo]
print("efoo = " .. tostring(efoo))

local efoo2 = encapsulator[foo]
print("efoo2 = " .. tostring(efoo))

efoo:hello()

local baz = { hello =
  function(self) print("Greetings from " .. tostring(self)) end }
print("baz = " .. tostring(baz))

local ebaz = encapsulator[baz]
print("ebaz = " .. tostring(ebaz))

ebaz:hello()

請注意,makeEncapsulator 會傳回雙向表格。第二個表格適用於如果需要穿透某些方法呼叫目標以外物件的代理伺服器障礙。

請注意,不應將封裝器表格外洩給不可信的程式碼。proxy2rep 表格有明顯的危險性,因為它會授予對表示的直接存取權。rep2proxy 表格很危險,因為它可以進行反覆處理。可以透過包裝它在另一個層級的代理伺服器表格來處理這個問題,但這會讓封裝物件更昂貴。也可以用函式包裝這個表格,但這又會執行得更慢。

這項實作中沒有任何內容會妨礙使用單一封裝器表格來處理多種物件類型。使用多個封裝器的主要原因會是多個不應能夠看見彼此 repeat 的環境。擁有幾個較小的表格,而非擁有幾個較大的表格,也可能會有速度上的優點。

參見


最新變更 · 偏好設定
編輯 · 歷程
最後編輯時間為 2009 年 3 月 14 日下午 5:57 GMT (diff)