练习21.4:双重表示的一种变体是使用代理来实现对象(称为“跟踪表访问”的部分)。每个对象都由一个空代理表表示。内部表将代理映射到携带对象状态的表。该内部表无法从外部访问,但方法使用它来将其自身参数转换为它们操作的真实表。使用这种方法实现帐户示例并讨论其优点和缺点。
local AccountProxy = {}
-- Internal table mapping proxies to actual Account tables
local accounts = {}
function AccountProxy:new()
local proxy = {}
local account = {balance = 0}
accounts[proxy] = account
setmetatable(proxy, {
__newindex = function(_, k, v)
account[k] = v
end,
__index = function(_, k)
return account[k]
end,
})
return proxy
end
function AccountProxy:withdraw(v)
self.balance = (self.balance or 0) - v
end
function AccountProxy:deposit(v)
self.balance = (self.balance or 0) + v
end
function AccountProxy:getBalance()
return self.balance or 0
end
-- Example usage
local acc_1 = AccountProxy:new()
acc_1:deposit(100.0)
print(acc_1:getBalance()) -- 100.0
acc_1:withdraw(50.0)
print(acc_1:getBalance()) -- 50.0`
我尝试调用零值(方法“存款”)。有什么建议吗?
我需要对Account对象和余额进行封装作为Account的属性
acc_1:deposit(100.0)
是 acc_1.deposit(acc_1, 100.0)
的语法糖。您面临的直接问题是 acc_1.deposit
是 nil
。首先,acc_1
是空的。这是设计使然——它是由 proxy
返回的 AccountProxy:new
。因为键不存在,所以它会检查元表。 __index
方法在相应的account
表中查找,但这只有balance
。 acc_1
与 AccountProxy.deposit
没有任何连接。你可以称之为 AccountProxy.deposit(acc_1, 100.0)
,但这并不是真正的传统的面向对象模式。
您希望
acc_1.deposit
与实际方法关联,因此您必须将 deposit
直接放入 acc_1
中,或者通过元表访问它。我认为这两个都很好,但我可能更喜欢第二个,因为您不想从实例方法访问 new
中定义的任何局部变量。例如
function AccountProxy:new()
local proxy = {}
-- ...
function proxy:deposit()
-- do the deposit ...
end
-- ...
return proxy
end
或
function AccountProxy:new()
local proxy = {}
setmetatable(proxy, {
__index = AccountProxy,
})
-- ...
return proxy
end
为此目的使用元表可能会干扰您现有的元表,但我认为您现有的元表正在破坏您的目的。据说这里代理的目的是封装——防止外部代码访问
balance
。您提供的元表只允许任何代码(内部或外部)访问余额。 (是的,这是引用示例中使用的元表,但据我所知,这只是强制访问通过特定路径的示例,而不是专门使该路径使用元表。)
如果没有元表,您需要另一种方式从
balance
检索 proxy
;并且您已经按照练习的建议部分实施了这一点。给定 proxy
,您可以查找 accounts[proxy]
,例如
function AccountProxy:getBalance()
return accounts[self].balance or 0
end
假设外部代码无法访问
accounts
(可以通过词法作用域强制执行),那么它就无法直接与 balance
交互,除非通过提供的方法。
(使用
accounts
并不是进行此类映射或获得此类封装的唯一方法。我倾向于使用闭包,但我不确定这是否是其他地方讨论的模式。)
其他风格注释:
AccountProxy:new
不使用 self
;概念上不是实例方法的东西可能更适合仅使用 .
而不是 :
。nil
的情况。我不一定对此有问题,但如果我知道平衡永远不会nil
(我认为在这里是正确的),我宁愿不这样做。