基于代理的封装

lua-users home
wiki

以下技术可用于通过代理表实现对象的封装。目标是拥有一个代理对象,该对象公开另一个对象(表示)的方法,但隐藏实现。棘手(因此有点昂贵)的部分是找到一种方法来存储从代理到其表示的链接,使其无法被其他代码访问。弱键表似乎是一个选择,但半弱表容易出现循环引用问题。元表似乎是一个选择,但元表要么完全对 Lua 代码隐藏,要么完全暴露。(我忽略了编写 C 语言实现的选项。这样做可以避免最后一个问题。)因此,这里采用的解决方案是将全弱表与基于元表的引用结合起来,使链接变强。

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

用法如下。我们创建一个封装器,然后将需要封装的对象通过它运行。客户端必须小心,每当对象跨越封装边界时都要封装它,因为在任何方法内部,`self` 都等于真实对象而不是代理。

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 表之所以危险,是因为它可以被迭代。这可以通过将其包装在另一个代理表层来解决,但这会使封装对象更加昂贵。也可以将表包装在一个函数中,但这同样会运行得更慢。

此实现中的任何内容都不会阻碍为多种对象类型使用单个封装器表。使用多个封装器的主要原因可能是存在多个不应能看到彼此表示的上下文。与拥有少数几个大表相比,拥有多个小表也可能在速度上有优势。

另请参阅


RecentChanges · preferences
编辑 · 历史
最后编辑于 2009 年 3 月 14 日下午 12:57 GMT (差异)