在Lua中使用 ":" 实现面向对象方式的调用。":"只是一个语法糖,它同时在方法的声明与实现中增加一个名为self的隐藏参数(self代表对象本身)。
Account = {balance = 1000} -- 账户余额初始为1000。
--[[ 取钱。
使用面向对象的方式隐藏了"self"参数,
"withdraw()"完整的参数列表是"Account.withdraw(self, v)"。]]
function Account:withdraw(v)
self.balance = self.balance - v
end
--[[ 使用面向对象的方式隐藏了"self"参数,
实际传递给"withdraw()"的参数是"Account"和"100.00"。]]
Account:withdraw(100.00)
print(Account.balance) --> 900.0
实现类
类在面向对象语言中就好象一个模板,通过模板所创建的实例就具有模板中规定的特性。Lua中没有类的概念,每一个对象规定自己的行为,每一个对象就是自己的实例。不过在Lua中模拟“类”并不难,使用table模拟类的成员属性,function模拟类的成员方法,我们可以用继承的概念,使用两个对象,让其中一个对象作为另一个对象的“类”,
setmetatable(a, {__index = b}) -- b作为a的类,在a中找不到的方法都将去b中寻找。
继续扩展银行账户的例子,
Account = {balance = 0} -- 账户余额初始为0。
function Account:new (o)
o = o or {} -- 如果o为空,创建一个新表
setmetatable(o, self)
self.__index = self -- 新对象"metatable"中的"__index"依旧是"Account"(或其子类)。
return o
end
function Account:deposit(v)
self.balance = self.balance + v
end
function Account:withdraw(v)
self.balance = self.balance - v
end
account1 = Account:new()
print(account1.balance) --> 0
print(Account.balance) --> 0
account1:deposit(500.00)
print(account1.balance) --> 500
print(Account.balance) --> 0
"account1:deposit(500.00)",实际上调用的是"account1.deposit(a, 500.00)", "account1"中没有"deposit()",所以去找"account1"的"metatable"中的"__index元方法",即"Account"。又因为"Account"是一个"table",所以"account1.deposit(a, 500.00)"相当于"Account.deposit(a, 500.00)"。
实现继承
Account = {balance = 0}
function Account:new (o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Account:deposit (v)
self.balance = self.balance + v
end
function Account:withdraw (v)
-- 普通账户不可以透支。
if v > self.balance then error"insufficient funds" end
self.balance = self.balance - v
end
CreditAccount = Account:new{limit = 1000.00}
-- 为了让信用卡账户能够透支,需要重写"withdraw()"方法。
function CreditAccount:withdraw (v)
-- 信用卡账户在一定额度内可以透支。
if v - self.balance >= self:getLimit() then
error"insufficient funds"
end
self.balance = self.balance - v
end
function CreditAccount:getLimit ()
return self.limit or 0
end
--"CreditAccount"中没有"new()",实际调用的是"Account.new()"
creditaccount1 = CreditAccount:new{limit = 2000.00}
--"CreditAccount"中没有"deposit()",实际调用的是"Account.deposit()"
creditaccount1:deposit(100.00)
-- 此时调用的是"CreditAccount:withdraw()"。
creditaccount1:withdraw(200.00)
print(creditaccount1.balance) --> -100.0
信用卡账户继承自普通账户,可以透支。 "CreditAccount"是"Account"的一个“实例”,但他同时也可以作为一个“类”以产生其他实例。通过CreditAccount = Account:new{limit = 1000.00}来实现继承Accout。
实现多重继承
当__index
是个函数时,Lua调用它,以”table”和缺失的”key”作为参数传入。而当__index
是一个”table”时,Lua直接以缺失的”key”在它这张”table”中寻找。
-- 在table 'plist'中查找'k',k表示需要查找的函数
local function search(k, plist)
for i = 1, #plist do
local v = plist[i][k] -- 尝试第i个基类
if v then return v end
end
end
function createClass(...)
local c = {} -- 新类
local parents = {...}
-- 在其父类列表中的搜索方法,t表示传入的table,k表示key
setmetatable(c, {__index = function(t, k)
return search(k, parents)
end})
-- 将'c'作为其实例的元表
c.__index = c
-- 为这个新类定义一个新的构造函数
function c:new(o)
o = o or {}
setmetatable(o, c)
return o
end
return c -- 返回新类
end
-- 类Named
Named = {}
function Named:getname()
return self.name
end
function Named:setname(n)
self.name = n
end
-- 类Account
Account = {balance = 0}
function Account:new (o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Account:withdraw(V)
if v > self.balance then error"invalid params" end
self.balance = self.balance - v
end
function Account:deposit(v)
self.balance = self.balance + v
end
-- 创建一个新类NamedAccount,继承Account和Named
NamedAccount = createClass(Account, Named)
account = NamedAccount:new({name = "Big Eye"})
print(account:getname()) -- 输出 Big Eye
account:deposit(100)
print(account.balance)
NamedAccount = createClass(Account, Named),创建一个新类NamedAccount,继承Account和Named。
执行account = NamedAccount:new()时:
会调用createClass函数中的新创建的构造函数,也就是闭包(closure),关于闭包的详细介绍,请移步https://blog.csdn.net/qq826364410/article/details/88702685,通俗的讲,闭包是由定义在其他函数之中的函数引用和它访问的外部函数的局部变量upvalue组成。闭包会在堆上分配内存,进行保存,所以在执行完createClass函数后,新创建的构造函数,还是可以继续访问。
执行print(account:getname())时:
- account中没有getname方法,所以找account的元表metatable.__index元方法,
- account的metatable是NameAccount,而NameAccount的__index元方法是它本身,
- 所以,找NameAccount的元表metatable.__index元方法,它是一个函数,
- 然后,调用search这个函数,在父类中搜索getname方法,
- 在父类Named中找到getname方法,调用getname方法
多重继承的效率可能比单一继承的效率低很多(因为多了search()
这个过程)。如果想提高效率的话可以将找到的父类的函数拷贝到子类中,
-- 在其父类列表中的搜索方法
setmetatable(c, {__index = function(t, k)
local v = search(k, parents)
t[k] = v -- "Named.getname()"存储在了"account"中。
return v
end})
这样,除了第一次调用,之后的调用实际上是调用子类中拷贝过来的父类的函数,省去了search()
的过程,效率就高很多。但是这种做法也有缺陷,在程序运行起来之后,就很难改变父类中的方法了,因为即使更改了,子类也不会继承过去(子类保存了父类原先的方法)。
定义私有成员或私有方法
使用两个”table”,其一存储私有成员,另一个存储公有成员和公有方法,两个”table”组成”Closure”,私有”table”作为公有”table”的”Closure”被访问,私有方法直接存储在”Closure”中
function newAccount (initialBalance)
-- 私有"table"。
local self = {
balance = initialBalance, -- 余额。
count = 0 -- 积分。
}
-- 私有方法,未导出到公有"table"中,外部无法访问。
local addCount = function (v)
self.count = self.count + v * 0.1 -- 消费的10%作为积分。
end
local withdraw = function (v)
self.balance = self.balance - v
addCount(v)
end
local deposit = function (v)
self.balance = self.balance + v
end
local getBalance = function () return self.balance end
local getCount = function () return self.count end
-- 公有"table"。
return {
withdraw = withdraw,
deposit = deposit,
getBalance = getBalance,
getCount = getCount
}
end
account = newAccount(100)
print(account.balance) --> nil
print(account.count) --> nil
print(account.getBalance(), account.getCount()) --> 100 0
account.deposit(500)
print(account.getBalance(), account.getCount()) --> 600 0
account.withdraw(100)
print(account.getBalance(), account.getCount()) --> 500 10