一. self(this)标识
1.1 在此之前使用全局名称来创建方法
Account = {balance = 0}
function Account.withdraw(v)
Account.balance = Account.balance - v
end
Account.withdraw(100.00)
上述函数就是简单的方法实现,但是在函数中使用全局名称Account是一个非常糟糕的习惯,原因:
1. 这个withdraw 函数只能针对 Account表(对象)工作 (不能给Account表之外的工作)
2. 要withdraw 函数给Account表工作的话,也要满足对象保存在特定的全局变量时。如果我们改变了对象的名称,withdraw就不能工作(需要对象和函数之间存在相同的生命周期)
a, Account = Account, nil
a.withdraw(100) -- ERROR
1.2 使用self标识 优化上述方法
-- 添加一个self参数,调用此方法时需要指定操作对象
function Account.withdraw(self, v)
self.balance = self.balance - v
end
a1 = Account
Account = nil
--...
a1.withdraw(a1, 100.00) -- ok
加入 self 作为函数的参数,调用时指定操作的对象,此时 函数内部相当于:a1.balance = a1.balance - v , 将操作对象传入函数这样做,使得摒弃了之前 单一对象的调用
1.3 冒号操作符:(语法糖)
在上述函数定义和调用时,定义时需要显示声明self形参,导致在调用时,又需要增加一个实参,不方便。
冒号操作符的作用:在一个方法调用中增加一个额外的实参,或在方法定义中增加一个额外的隐藏形参
Account = {balance=0,
withdraw = function(self, v)
self.balance - v
end}
function Account:deposit(v)
self.balance = self.balance + v
end
Account.deposit(Account, 200.00)
Account:withdraw(100.00)
二. 类(class)
1. 类的结构
到目前为止,对象已经具有了 标识(self)、状态和对状态进行的操作,还缺类体系、继承、私有性,下面来学习这些。
2. 类体系(通过元表和元方法实现)
要在Lua语言中表示一个类,我们只需要创建一个专门被用作其他对象的原型对象即可。类和原型都是一种组织多个对象间共享行为的方式
2.1 需求:如果有A,B两个对象,要B称为A的一个原型,A可以在B中查找它没有的操作。简单的说,B是A的类
setmetatable(A,{__index = B})
2.2 以Account对象为例 ,使用__index元方法让新对象继承Account的操作
Account = {balance=0,
withdraw = function(self, v)
self.balance - v
end}
function Account:deposit(v)
self.balance = self.balance + v
end
Account.deposit(Account, 200.00)
Account:withdraw(100.00)
-- 创建元表mt,__index元方法为 Account表
local mt = {__index = Account}
function Account.new(0)
o = o or {}
setmetatable(0, mt)
return o
end
a = Account.new{balance = 0}
a:deposit(100.00)
当我们创建一个新账户a时,a会将mt作为其元表。当调用a:deposit(100.00)时,实际上调用的是a.deposit(a, 100.00), 不过,无法在表a中找到字段"deposit",所以它会在元表的__index中搜索。此时情况大致为:
getmetatable(a).__index.deposit(a, 100.00)
2.3 优化2.2示例
1.不需要元表mt, 直接把Account用作元表
2.对new也用冒号语法
function Account:new(o)
o = o or {}
self.__index = self
setmetatable(o, self)
return o
end
三. 继承(Inheritance)
3.1 需求:实现 SpecialAccount 继承Account, s 继承SpecialAccount
Account = {balance = 0}
function Account:new(o)
o = o or {}
self.__index = self
setmetatable(o, self)
return o
end
function Account:deposit(v)
self.balance = self.balance + v
end
function Account:withdraw()
if v > self.balance then error "insufficient funds" end
self.balance = self.balance - v
end
创建一个子类SpecialAccount 继承 Account,此时 new()中的self 和元表都 指向 Account
SpecialAccount = Account:new()
创建一个子类 s 继承 Account,此时new() 中的self和元表都 指向SpecialAccount (谁调用就指向谁)
3.2 重定义基类 Account 的 withdraw方法 (可以重定义基类的任意方法)
由于子类SpecialAccount 继承自 Account, 只需要在 SpecialAccount 中重写编写 withdraw() 就好
-- 重定义withdraw
function SpecialAccount:withdraw(v)
if v - self.balance >= self:getLimit() then
error "insufficient funds"
end
self.balance = self.balance - v
end
-- 当调用 s:withdraw(200.00)时,Lua语言会在SpecialAccount 中先找到 withdraw()方法,所以不会在Account中查找
s:withdraw(200.00)
四. 多重继承(一个类有多个超类)
-- 在表'plist'的列表中查找'k'
local function search(k, plist)
for i = 1, #plist do
local v = plist[i][k] -- 寻找第‘i’个超类的k值
if v then return v end
end
end
function createClass(...)
local c = {} -- 创建一个局部空表
local parents = {...} -- 父类列表
-- 给C 设置元表和元方法 __index, 当调用C表不存在的值时,会执行元方法 __index ,即执行 search, 在父类列表中找到一个符合的父类,返回结果
setmetatable(c, {__index = function(t, k)
return search(k, parents) -- 因为 parents 是一个上值,所以可以访问到
end})
--c表设置元方法__index, 为后面给其他表做元表做准备
c.__index = c
-- 定义一个构造函数
function c:new(o)
o = o or {}
-- 可以理解为,o继承于C
setmetatable(o, c)
return o
end
return c
end
新建一个只有两个方法 setname 和 getname 的类Named:
Named = {}
function Named:getName()
return self.name
end
function Named:setname(n)
self.name = n
end
创建一个同时继承 Account 和 Named的新类 NamedAccount
NamedAccount = createClass(Account, Named)
account = NamedAccount:new{name = "Paul"}
print(account:getname()) -- > Paul
那 account:getname() 是怎么进行求值的呢?
首先,Lua语言 在 account 中没找到字段"getname", 它就找到 account 的元表的__index字段,示例中该字段为NameAccount, 然后 NameAccount也不存在字段 "NameAccount",
那么,会从NameAccount的元表中查找 __index字段,由于该字段是个函数,Lua语言就调用这个函数 search。 然后先在 Account中查找 "getname",没有找到, 在 Named中找到了
五. 私有性
Lua没有实现私有化的机制,但是Lua足够灵活,可以模拟出私有化机制,下面提供三种私有化方式
1. 外部不去访问对象的内容就是私有的,哈哈,把 所有需要私有名称最后加一个下画线区分,例如:name_
2. 在模块定义及返回时''做手脚''
3. 对偶表示
function newAccount(initialBalance)
local self = {balance = initialBalance}
local withdraw = function(v)
self.balance = self.balance - v -- self是上值
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
在函数newAccount 中 定义了几个闭包函数,和一个self 表,其中 那些闭包函数是公共的,self是私有的。因为 return 的时候压根不返回 self表, 而那些闭包函数,由于self是上值的原因,可以正常访问 self ,从而实现了 对外私有
优化:在上面方法中,self表只有一个字段,其实也可以对函数私有
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 getBalance = function()
return self.balance + extra()
end
end
创建 函数extra, 同样在return 时忽略它,实现外部私有
六. 单方法对象
单方法对象是指:对象中只有一个方法的情况
1. 这种情况可以不用创建接口表,只要把这个单独的方法以对象的表示形式返回
2. 这个方法其实是一个根据不同参数完成不同任务的分发方法
function newObject(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(0)
print(d("get")) -- 0
d("set", 10)
print(d("gte")) -- 10
七. 对偶表示(实现私有性):每张表和其对应的默认值相互关联
概念:
通常,我们使用键把属性关联到表,例如:
table[key] = value
换成对偶表示,则是
local key = {}
key[table] = value
此时的key表为独立的表,该表的键为 "各种表", 值为 "各种表"的 "默认值",上例中, 新建一个局部空表key,将 表table 和 它对应的默认值:value 保存在key表中。
local balance = {}
function Account.withdraw()
balance[self] = balance[self] - v
end
目的:用对偶表示的方式,保证了 访问 函数withdraw 时,由于balance是局部的,表的默认值都保存在 balance中,要想得到默认值,访问者还必须要能同时访问 表balance
缺陷:
当把 表(对象)作为 balance 表的键时,那么balance表就是永久存在,不会被垃圾收集器回收(后面会讲),直到某些代码将其从表中显示移除(简单来说,不会自动回收,在讲垃圾收集(第五章)时会优化)
Demo:使用对偶表示实现账户(重构账户功能)
local balance = {}
Account = {}
function Account:withdraw(v)
balance[self] = balance[self] - v
end
function Accont:deposit(v)
balance[self] = balance[self] + v
end
function Account:balance()
return balance[self]
end
function Account:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
balance[o] = 0 -- 初始余额
return o
end
a = Account:new{}
a:deposit(100.00)
print(a:balance()) -- 100