Lua中的table就是一種對象,但是如果直接使用仍然會存在大量的問題,見如下代碼:
復制代碼 代碼如下:
Account = {balance = 0}
function Account.withdraw(v)
Account.balance = Account.balance - v
end
--下面是測試調用函數(shù)
Account.withdraw(100.00)
在上面的withdraw函數(shù)內部依賴了全局變量Account,一旦該變量發(fā)生改變,將會導致withdraw不再能正常的工作,如:
復制代碼 代碼如下:
a = Account; Account = nil
a.withdraw(100.00) --將會導致訪問空nil的錯誤。
這種行為明顯的違反了面向對象封裝性和實例獨立性。要解決這一問題,我們需要給withdraw函數(shù)在添加一個參數(shù)self,他等價于Java/C++中的this,見如下修改:
復制代碼 代碼如下:
function Account.withdraw(self,v)
self.balance = self.balance - v
end
--下面是基于修改后代碼的調用:
a1 = Account; Account = nil
a1.withdraw(a1,100.00) --正常工作。
針對上述問題,Lua提供了一種更為便利的語法,即將點(.)替換為冒號(:),這樣可以在定義和調用時均隱藏self參數(shù),如:
復制代碼 代碼如下:
function Account:withdraw(v)
self.balance = self.balance - v
end
--調用代碼可改為:
a:withdraw(100.00)
1. 類:
Lua在語言上并沒有提供面向對象的支持,因此想實現(xiàn)該功能,我們只能通過table來模擬,見如下代碼及關鍵性注釋:
復制代碼 代碼如下:
--[[
在這段代碼中,我們可以將Account視為class的聲明,如Java中的:
public class Account
{
public float balance = 0;
public Account(Account o);
public void deposite(float f);
}
--]]
--這里balance是一個公有的成員變量。
Account = {balance = 0}
--new可以視為構造函數(shù)
function Account:new(o)
o = o or {} --如果參數(shù)中沒有提供table,則創(chuàng)建一個空的。
--將新對象實例的metatable指向Account表(類),這樣就可以將其視為模板了。
setmetatable(o,self)
--在將Account的__index字段指向自己,以便新對象在訪問Account的函數(shù)和字段時,可被直接重定向。
self.__index = self
--最后返回構造后的對象實例
return o
end
--deposite被視為Account類的公有成員函數(shù)
function Account:deposit(v)
--這里的self表示對象實例本身
self.balance = self.balance + v
end
--下面的代碼創(chuàng)建兩個Account的對象實例
--通過Account的new方法構造基于該類的示例對象。
a = Account:new()
--[[
這里需要具體解釋一下,此時由于table a中并沒有deposite字段,因此需要重定向到Account,
同時調用Account的deposite方法。在Account.deposite方法中,由于self(a對象)并沒有balance
字段,因此在執(zhí)行self.balance + v時,也需要重定向訪問Account中的balance字段,其缺省值為0。
在得到計算結果后,再將該結果直接賦值給a.balance。此后a對象就擁有了自己的balance字段和值。
下次再調用該方法,balance字段的值將完全來自于a對象,而無需在重定向到Account了。
--]]
a:deposit(100.00)
print(a.balance) --輸出100
b = Account:new()
b:deposit(200.00)
print(b.balance) --輸出200
2. 繼承:
繼承也是面向對象中一個非常重要的概念,在Lua中我們仍然可以像模擬類那樣來進一步實現(xiàn)面向對象中的繼承機制,見如下代碼及關鍵性注釋:
復制代碼 代碼如下:
--需要說明的是,這段代碼僅提供和繼承相關的注釋,和類相關的注釋在上面的代碼中已經(jīng)給出。
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
--下面將派生出一個Account的子類,以使客戶可以實現(xiàn)透支的功能。
SpecialAccount = Account:new() --此時SpecialAccount仍然為Account的一個對象實例
--派生類SpecialAccount擴展出的方法。
--下面這些SpecialAccount中的方法代碼(getLimit/withdraw),一定要位于SpecialAccount被Account構造之后。
function SpecialAccount:getLimit()
--此時的self將為對象實例。
return self.limit or 0
end
--SpecialAccount將為Account的子類,下面的方法withdraw可以視為SpecialAccount
--重寫的Account中的withdraw方法,以實現(xiàn)自定義的功能。
function SpecialAccount:withdraw(v)
--此時的self將為對象實例。
if v - self.balance >= self:getLimit() then
error("Insufficient funds")
end
self.balance = self.balance - v
end
--在執(zhí)行下面的new方法時,table s的元表已經(jīng)是SpecialAccount了,而不再是Account。
s = SpecialAccount:new{limit = 1000.00}
--在調用下面的deposit方法時,由于table s和SpecialAccount均未提供該方法,因此訪問的仍然是
--Account的deposit方法。
s:deposit(100)
--此時的withdraw方法將不再是Account中的withdraw方法,而是SpecialAccount中的該方法。
--這是因為Lua先在SpecialAccount(即s的元表)中找到了該方法。
s:withdraw(200.00)
print(s.balance) --輸出-100
3. 私密性:
私密性對于面向對象語言來說是不可或缺的,否則將直接破壞對象的封裝性。Lua作為一種面向過程的腳本語言,更是沒有提供這樣的功能,然而和模擬支持類與繼承一樣,我們仍然可以在Lua中通過特殊的編程技巧來實現(xiàn)它,這里我們應用的是Lua中的閉包函數(shù)。該實現(xiàn)方式和前面兩個示例中基于元表的方式有著很大的區(qū)別,見如下代碼示例和關鍵性注釋:
復制代碼 代碼如下:
--這里我們需要一個閉包函數(shù)作為類的創(chuàng)建工廠
function newAccount(initialBalance)
--這里的self僅僅是一個普通的局部變量,其含義完全不同于前面示例中的self。
--這里之所以使用self作為局部變量名,也是為了方便今后的移植。比如,以后
--如果改為上面的實現(xiàn)方式,這里應用了self就可以降低修改的工作量了。
local self = {balance = initialBalance} --這里我們可以將self視為私有成員變量
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
--返回對象中包含的字段僅僅為公有方法。事實上,我們通過該種方式,不僅可以實現(xiàn)
--成員變量的私有性,也可以實現(xiàn)方法的私有性,如:
--local privateFunction = function() --do something end
--只要我們不在輸出對象中包含該方法的字段即可。
return {withdraw = withdraw, deposit = deposit, getBalance = getBalance}
end
--和前面兩個示例不同的是,在調用對象方法時,不再需要self變量,因此我們可以直接使用點(.),
--而不再需要使用冒號(:)操作符了。
accl = newAccount(100.00)
--在函數(shù)newAccount返回之后,該函數(shù)內的“非局部變量”表self就不再能被外部訪問了,只能通過
--該函數(shù)返回的對象的方法來操作它們。
accl.withdraw(40.00)
print(acc1.getBalance())
事實上,上面的代碼只是給出一個簡單的示例,在實際應用中,我們可以將更多的私有變量存放于上例的局部self表中。
您可能感興趣的文章:- Lua教程(一):簡介、優(yōu)勢和應用場景介紹
- Lua教程(二):基礎知識、類型與值介紹
- Lua教程(三):表達式和語句
- Lua教程(四):函數(shù)詳解
- Lua教程(五):迭代器和泛型for
- Lua教程(六):編譯執(zhí)行與錯誤
- Lua教程(七):數(shù)據(jù)結構詳解
- Lua教程(八):數(shù)據(jù)持久化
- Lua教程(九):元表與元方法詳解
- Lua教程(十): 全局變量和非全局的環(huán)境
- Lua教程(十一):模塊與包詳解