许多人认为,私有性(也被称为信息隐藏)是一门面向对象语言不可或缺的一部分;每个对象的在状态都应该由它自己控制。在一些诸如 C++
和 Java
的面向对象语言中,我们可以控制一个字段(也被称为实例变量)或一个方法是否在对象之外可见。另一种非常流行的面向对象语言 Smalltalk
,则规定所有的变量都是私有的,而所有的方法都是公有的。第一种面向对象语言 Simula
,则不提供任何形式的私有性保护。
此前,我们所学的 Lua
语言中标准的对象实现方式没有提供私有性机制。一方面,这是使用普通结构(表)来表示对象所带来的后果;另一方面,这也是 Lua
语言为了避免冗余和人为限制所采取的方法。如果不想访问一个对象的内容,那就不要取访问就是了。
一种常见的做法是把所有的私有名称的前面加上一个下划线,这样就能立刻区分出全局名称了。
不过,尽管如此, Lua
语言的另外一项设计目的是灵活性,它为程序员提供能够模拟许多不同机制的元机制。虽然在 Lua
语言中,对象的基本设计没有提供私有性机制,但可以用其他方式来实现具有访问控制能力的对象。尽管程序员一般不会用到这种实现,但是了解这种实现还是有好处的,因为这种实现既探索了 Lua
语言中某些有趣的方面,又可以成为其他更具体问题的良好解决方案。
这种做法基本思想是通过两个表来表示一个对象:一个表用来保存对象的状态,另一个表用于保存对象的操作(或接口)。我们通过第二个表来访问对象本身,即通过组成其接口的操作来访问。为了避免未授权的访问,表示对象状态的表不保存在其他表的字段中,而只保存在方法的闭包中。例如,如果要用这种设计来表示银行账户,那么可以通过下面的工厂函数创建新的对象:
function newAccount(initialBalance) local self = {balance = initialBalance} local withdraw = function (v) self.balance = self.balance - v end local deposit = function (v) self.balance = self.balance + v end local getBalance = function () return self.balance end return { withdraw = withdraw, deposit = deposit, getBalance = getBalance } end
首先,这个函数创建了一个用于保存对象内部状态的表,并将其存储在局部变量 self
中。然后,这个函数创建了对象的方法。最后,这个函数会创建并返回一个外部对象,该对象将方法名与真正的方法实现映射起来。这里的关键在于,这些方法不需要额外的 self
参数,而是直接访问 self
变量。由于没有了额外的参数,我们也就无需使用冒号语法来操作这些对象,而是可以像普通函数那样来调用这些方法:
acc1 = newAccount(100.00) acc1.withdraw(40.00) print(acc1.getBalance()) --> 60
这种设计给予了存储在表 self
中所有内容完全的私有性。当 newAccount
返回后,就无法直接访问这个表了,我们只能通过在 newAccount
中创建的函数来访问它。虽然我们的示例只把一个实例变量放到了私有表中,但还可以将一个对象中的所有私有部分都存入这个表。我们也可以定义私有方法,他们类似于公有方法但不放入接口中。例如,我们的账户可以给余额大于某个值的用户额外 10%
的信用额度,但是又不想让用户访问到这些计算细节,就可以将这个功能按照以下方法实现:
function newAccount(initialBalance) local self = { balance = initialBalance, LIM = 10000.00 } local extra = function () if self.balance > self.LIM then return self.balance * 0.10 else return 0 end end local withdraw = function (v) self.balance = self.balance - v end local deposit = function (v) self.balance = self.balance + v end local getBalance = function () return self.balance * extra() end return { withdraw = withdraw, deposit = deposit, getBalance = getBalance } end
与前一个示例一样,任何用户都无法直接访问 extra
函数。