基于代理的封装

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

此实现中没有任何内容妨碍对多个对象类型使用单个封装器表。使用多个封装器的主要原因是多个上下文不应该能够看到彼此的表示。拥有多个较小的表而不是几个较大的表也可能具有速度优势。

另请参阅


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2009 年 3 月 14 日下午 5:57 GMT (差异)