基于代理的封装 |
|
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 表之所以危险,是因为它可以被迭代。这可以通过将其包装在另一个代理表层来解决,但这会使封装对象更加昂贵。也可以将表包装在一个函数中,但这同样会运行得更慢。
此实现中的任何内容都不会阻碍为多种对象类型使用单个封装器表。使用多个封装器的主要原因可能是存在多个不应能看到彼此表示的上下文。与拥有少数几个大表相比,拥有多个小表也可能在速度上有优势。