基于代理的封装 |
|
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
表很危险,因为它可以被迭代。可以通过将它包装在另一层代理表中来解决这个问题,但这会使封装对象变得更加昂贵。也可以将表包装在函数中,但这会运行得更慢。
此实现中没有任何内容妨碍对多个对象类型使用单个封装器表。使用多个封装器的主要原因是多个上下文不应该能够看到彼此的表示。拥有多个较小的表而不是几个较大的表也可能具有速度优势。