对象
lua 中的 table 其实就是对象,理由如下
1. table 与对象一样可以拥有状态
2. table 与对象一样拥有一个独立于其值的标识 self,值相同的两个 table 是两个不同的对象
3. table 与对象一样拥有独立于创建者和创建地的生命周期
方法
table 中字段的值可以是任意类型,如果某个字段的值是一个函数,那这个函数就称为对象的方法
Account =
{
balance = 100,
withdraw = function(v)
Account.balance = Account.balance - v
end
}
withdraw 是全局对象 Account 的一个方法。对象具有独立的生命周期,因此对象的方法也只有这个对象本身能访问,比如下面的操作是错误的
a = Account
Account = nil
a.withdraw(10)
在上面的代码,我们将对象 Account 改名为 a,然后通过 a 去调用 withdraw 方法,执行的时候会发现 Account 对象不存在,当然 Account.balance 也会找不到
self 参数
对象中的方法要访问对象的其它字段,需要显式地在方法中使用全局对象名,这种编程习惯是非常不好的,这种方式无法从一个对象拓展出其它对象,更无法实现类的概念,因为只能使用创建对象时唯一的全局对象名,比如上面的 Account,任何操作都只能使用 Account 这个名称
这个问题的解决方案是给每一个方法指定其操作的”接收者“,通常使用 self 或 this 来表示这个”接收者“,在 lua 中使用 self 参数
Account = {balance = 100}
Account.withdraw = function(self, v)
self.balance = self.balance - v
end
a = Account
Account = nil
a.withdraw(a, 1)
print(a.balance)
方法定义时指定第一个参数为 self,然后在调用方法时把具体的对象传进去。一种更简单写法是使用冒号,使用这种方式可以隐藏 self 参数
function Account:withdraw(v)
self.balance = self.balance - v
end
a:withdraw(1)
类
实际上 lua 并没有类的概念,但我们可以使用”原型“来实现类与对象之间的关系。把一个表设为另一个表的原型(metatable),那这个原型表就相当于是一个类,而继承自这原型表的表就是对象
lua 中的类就是对象元表来实现的,通过设置元表的 __index 元方法,当访问一个对象不存在的字段或方法时,可以通过 __index 元方法在类中查找
-- 定义类
Account = {balance = 0}
-- 构造函数
function Account:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Account:withdraw(v)
self.balance = self.balance + v
end
function Account:deposit(v)
self.balance = self.balance - v
end
-- 创建对象
a = Account:new()
a:withdraw(100)
a:deposit(50)
print(a.balance)
分析上面代码的执行过程:
* Account:new() 会创建一个 table(对象),这个 table 的 metatable 是 Account
* a 没有 withdraw 方法,调用 a:withdraw(100) 时会到 Account 中查找 withdraw 方法,找到之后执行 self.balance = self.balance + v,此时 self 的值是 a,a 没有 balance 字段,因此同样的会到 Account 中查找;赋完值之后 a 就已经从 Account 继承了 balance 字段,下次访问就不需要到 Account 中查找了
* 调用 a:deposit(50) 同样会到 Account 中查找,因为 a 并没有 deposit 方法;但 a 已经有 balance 字段了,因为 a.balance = a.balance - v 可直接执行
继承
其实实现类的过程就是一种继承的行为,一个对象继承自另一个对象,被继承的对象就可以看作是一个类。我们可以继承一个类后重写它的一些方法生成一个子类,再继承这个子类来创建对象。执行对象的方法时会自下往上查找这个方法,如果在本对象找不到方法则向它的类原型查找,如果还没找到,则向父类查找,一直找到最原始的父类。
-- 定义父类
Account = {balance = 0}
function Account:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Account:withdraw(v)
if self.balance < v then
error("你的钱不够了!", 2)
else
self.balance = self.balance - v
end
end
function Account:deposit(v)
self.balance = self.balance + v
end
-- 继承
SpecialAccount = Account:new()
-- 重写方法
function SpecialAccount:withdraw(v)
if self.balance < v then
if self:getLimit() < v - self.balance then
error("你不能透支这么多钱!", 2)
else
self.balance = self.balance - v
end
else
self.balance = self.balance - v
end
end
-- 子类的新方法
function SpecialAccount:getLimit()
return self.limit or 0
end
-- 创建子类对象
a = SpecialAccount:new({limit = 100})
a:withdraw(90)
-- a:withdraw(20) -- error
print(a.balance)
多重继承
lua 中的继承不是原生,实现的关键是元表的 __index 元方法,单一继承的方式是设置 __index 为元表本身,这样在子类找不到的字段和方法就会到父类,也就是子类的元表去找。如果 __index 不设置元表本身,而是一个函数,通过这个函数遍历它子类的父类,再找到对应的字段或方法,这样就可以实现多继承了
可以使用工厂模式来动态创建新类
-- 新类工厂
CreateClass = function(...)
-- 定义新类
local class = {}
-- 定义父类集合
local parent = {...}
-- 设置新类的元表
setmetatable(
class,
{__index = function(t, k)
for _, v in ipairs(parent) do
if v[k] then
return v[k]
end
end
end}
)
-- 新类的元方法为自身
class.__index = class
-- 定义新类构造函数
class.ctor = function(self, o)
o = o or {}
-- 设置对象的元表为新类
setmetatable(o, class)
return o
end
return class
end
-- 使用工厂生产新类
NamedAccount = CreateClass(Account, Name)
-- 创建新类实例
ac = NamedAccount:ctor()
ac:set_name("myAccount")
print(ac:get_name())
ac:withdraw(100)
print(ac:get_balance())
- 在工厂方法里面,创建一个新类,然后指定新类的元表,元表的 __index 值不是具体的某个父类,而是一个新方法,准确来说是一个闭包,还包括非局部变量 parent 通过这个方法可以遍历所有的父类,找到要找的字段或方法。
- 这样一个拥有多个父类的新类就定义出来了,再给这个新类定义一个构造方法,设置其实例的元表为这个新类,而这个元表的 __index 值也是这个新类本身,这是单一继承的东西
- 调用这个工厂方法,把所有父类通过参数传进来,就生成了一个拥有多个父类的新类
- 调用新类的构造方法,实例化新类的一个对象,这个对象的元表是这个新类,而新类的元表是自定义的,其 __index 元方法可以遍历所有父类
- 在使用的时候,首先通过实例对象去调用某个方法,比如 set_name 方法,发现这个对象没有这个方法,然后就通过它的元表向它的父类查找。它的元表是 NamedAccount,NamedAccount 的 __index 是 NamedAccount 本身,所以解释器会查找 NamedAccount,发现也没有找到 set_name 这个方法,于是继续通过元表向父类查找。NamedAccount 元表的 __index 元方法遍历 NamedAccount 所有的父类,发现 Named 类中有 set_name 这个方法,于是查找结束,调用
Name:set_name(v)
单一方法
使用一个方法来表示一个对象,这种方式其实是用闭包 colsure 来表示对象。单一方法实现的对象虽然无法继承,但具有完全的私密性,比如实现一个调度器
newObject = function(value)
return function(action, v)
if action == "get" then
return value
elseif action == "set" then
value = v
else
error("invalid action")
end
end
end
d = newObject(10)
print(d("get"))
d("set", 100)
print(d("get"))
私密性
lua 本身没有提供私密性的机制,但我们可以利用 lua 的灵活性来模拟类的私密性。我们在创建对象的时候不返回对象本身,而是返回所有外部接口的集合,通过这些外部接口就可以访问和操作这个对象。
Account = function(balance)
local self = {_balance = balance}
local _withdraw = function(v)
self._balance = self._balance - v
end
local _deposit = function(v)
self._balance = self._balance + v
end
local _get_balance = function()
return self._balance
end
return {withdraw = _withdraw, deposit = _deposit, get_balance = _get_balance}
end
a = Account(0)
a.withdraw(100)
print(a.get_balance())
a.deposit(10)
print(a.get_balance())
总结
lua 中类的概念主要通过继承来实现的,实现继承主要有以下两步
1. 设置元表 setmetatable(child.parent)
2. 设置 __index 元方法 parent.__index=parent