在介紹完Lua的基礎(chǔ)知識(shí)包括元表,函數(shù)式編程之后,終于到了Lua面向?qū)ο缶幊獭km然并不打算使用Lua進(jìn)行大型應(yīng)用系統(tǒng)(程序)的開(kāi)發(fā)等脂,最多可能是嵌入到某個(gè)系統(tǒng)之間,如在Redis中使用Lua腳本完成一些操作撑蚌,或者使用Nginx+Lua完成服務(wù)限流或者日志收集上遥,負(fù)載均衡;另外比如我這里目前計(jì)劃使用Lua+Angular進(jìn)行一個(gè)Web前端的項(xiàng)目開(kāi)發(fā)争涌;這些工作仍然繞不開(kāi)面向?qū)ο蟆?/p>
面向?qū)ο缶幊蘋(píng)OP(Object Oriented Programming)粉楚,作為一種編程思想隨著互聯(lián)網(wǎng)的發(fā)展,已經(jīng)深入到現(xiàn)代系統(tǒng)編程的方方面面亮垫;OOP將對(duì)象作為程序的基礎(chǔ)單元模软,包含了數(shù)據(jù)和操作函數(shù);面向?qū)ο蟪绦蛟O(shè)計(jì)時(shí)饮潦,將業(yè)務(wù)中的事務(wù)進(jìn)行抽象封裝成多個(gè)對(duì)象燃异,程序在執(zhí)行時(shí),數(shù)據(jù)和消息將在每個(gè)對(duì)象間流轉(zhuǎn)继蜡,并按一定的操作流程依次執(zhí)行回俐。
類
類和實(shí)例是面向?qū)ο蟮闹匾拍罟渫龋瑢?duì)于Java、C#等面向?qū)ο笳Z(yǔ)言來(lái)說(shuō)仅颇,一般都具有關(guān)鍵詞class
來(lái)標(biāo)識(shí)和定義一個(gè)類单默,并組織類模板;對(duì)于Lua來(lái)說(shuō)忘瓦,并不具備類的概念搁廓,table
是Lua的最基礎(chǔ)對(duì)象,借助于table
Lua也可以模擬類政冻,但每個(gè)對(duì)象需要自己定義行為和狀態(tài)枚抵。
以Person
對(duì)象為例,借助table很容易實(shí)現(xiàn)一個(gè)類明场,這個(gè)類具有兩個(gè)屬性,和一個(gè)方法李丰。
Person = { name = "ray", age = 0 }
Person.show = function(name, age)
Person.name = name
Person.age = age
print("姓名:" .. Person.name .. ",年齡:" .. Person.age)
end
Person.show("ray", 12)
-->> 姓名:ray,年齡:12
在這段代碼中苦锨,看似實(shí)現(xiàn)了一個(gè)類,但其實(shí)只是對(duì)方法的一個(gè)封裝趴泌,無(wú)法從該類創(chuàng)建不同的實(shí)例舟舒,類只是模板,使用類模板嗜憔,可以創(chuàng)建出不同的實(shí)例秃励,是面向?qū)ο蟮闹饕卣鳌H缟鲜鍪纠罚绻凑認(rèn)ava的方法聲明對(duì)象:
person = Person -- 按照Lua的特點(diǎn)夺鲜,只是將person指向了Person,并沒(méi)有聲明實(shí)例
Person = nil -- Person消亡呐舔,person也消亡
person.show("ray", 12) -- 異常币励,說(shuō)明這并不是類,只是一個(gè)方法珊拼,只有一個(gè)對(duì)象的聲明周期
-->> attempt to index a nil value (global 'Person')
這個(gè)例子說(shuō)明按這種方式食呻,只是定義了一個(gè)方法。類是對(duì)事物的一種抽象澎现,如Person
應(yīng)該是對(duì)人
的一種抽象仅胞,而應(yīng)用該類模板,可以聲明ray
等等實(shí)例剑辫,其描述一個(gè)現(xiàn)實(shí)具體的人干旧,按這種方式理解,當(dāng)生命多個(gè)實(shí)例時(shí)揭斧,每個(gè)實(shí)例的聲明周期都是獨(dú)立的莱革,并不相互影響峻堰。Java中使用this
來(lái)描述當(dāng)前實(shí)例,Lua中可以使用self
作為接收者盅视,描述當(dāng)前實(shí)例對(duì)象捐名。
Person = { name = "ray", age = 0 }
Person.show = function(self, name, age)
self.name = name
self.age = age
print("姓名:" .. self.name .. ",年齡:" .. self.age)
end
person = Person
Person = nil
person.show(person, "ray", 12)
-->> 姓名:ray,年齡:12
每個(gè)方法都放置self
參數(shù)太麻煩了,Lua也可以像Java一樣闹击,編碼時(shí)對(duì)this
實(shí)現(xiàn)隱藏镶蹋,Lua可以隱藏self
參數(shù),實(shí)現(xiàn)在編碼時(shí)不必顯式聲明self
赏半。Lua在聲明時(shí)贺归,使用:
達(dá)到隱藏self
的目的。
Person = {}
function Person:setInfo(name, age)
self.name = name
self.age = age
end
function Person:show()
print("name:" .. self.name .. ", age: " .. self.age)
end
person = Person
Person:setInfo("ray", 12)
person:show()
-->> ray 12
使用:
只是簡(jiǎn)化了顯式self
參數(shù)的傳入断箫,包括調(diào)用和聲明時(shí)的傳遞拂酣,其他的和傳入self
功能一致。如仲义,聲明時(shí)使用:
婶熬,調(diào)用時(shí),使用.
并傳入self
效果一致埃撵。
-- 上例最后一步
person.show(person)
-->> ray 12
到這里L(fēng)ua使用table
解決了類的獨(dú)立生命周期赵颅、隱藏self
的問(wèn)題,但是目前編寫(xiě)的對(duì)象讓然不能稱之為類
暂刘,最基礎(chǔ)的饺谬,沒(méi)有辦法從上述定義中,獨(dú)立聲明多個(gè)實(shí)例谣拣。比如上例聲明了person
對(duì)象后募寨,將無(wú)法再次聲明第二個(gè)實(shí)例。
person = Person
Person:setInfo("ray", 12)
person2 = Person
person2:setInfo("hh", 13)
person:show()
person2:show()
-->> name:hh, age: 13
-->> name:hh, age: 13
對(duì)于Java來(lái)說(shuō)芝发,類就是個(gè)抽象事物的模板绪商,使用new
關(guān)鍵詞,可以創(chuàng)建任意的實(shí)例辅鲸,每一個(gè)實(shí)例都是具有模板中抽象的事務(wù)的獨(dú)立對(duì)象格郁。Lua由于沒(méi)有類的概念,使用table模擬類時(shí)独悴,如上例例书,聲明的對(duì)象將是同一個(gè)對(duì)象,這和類的表現(xiàn)不一致刻炒。為了解決獨(dú)立實(shí)例的問(wèn)題决采,只能自己定義類的形態(tài)和行為。
在元表
一章中坟奥,介紹過(guò)不同原型實(shí)現(xiàn)集成的功能树瞭,使用setmetatable
拇厢、__index
進(jìn)行元表設(shè)置,可以很容易的實(shí)現(xiàn)一個(gè)原型從另一個(gè)原型繼承晒喷。
當(dāng)訪問(wèn)一個(gè)table中的字段時(shí)孝偎,Lua會(huì)先從table中查找該字段,如果存在凉敲,則返回該字段的值衣盾;如果沒(méi)有,則檢查該table是否具有元表爷抓,如果沒(méi)有元表势决,則返回nil;如果有元表蓝撇,則會(huì)從元表中查找__index元方法果复,如果沒(méi)有該元方法,返回nil渤昌;如果有__index元方法据悔,則從該方法中查找指定字段。__index方法可以返回一個(gè)函數(shù)耘沼、也可以返回一個(gè)table
仍然使用上述示例,使用元表編程的方式朱盐,對(duì)這個(gè)Person
對(duì)象進(jìn)行修改群嗤,提供一個(gè)類似Java的new
實(shí)例的方法,當(dāng)創(chuàng)建一個(gè)新的對(duì)象時(shí)兵琳,將該對(duì)象繼承Person
的所有對(duì)象及方法狂秘,通過(guò)setmetatable
讓新對(duì)象的原型指向self
,并設(shè)置__index
索引也指向self
躯肌。
Person = {}
function Person:new(p)
-- 初始化者春,防止p(table)為空
p = p or {}
-- sefl為p的原型
setmetatable(p, self)
self.__index = self
-- 返回創(chuàng)建的實(shí)例,此時(shí)p將具備Person的所有對(duì)象
return p
end
function Person:show()
print("name:" .. self.name .. ", age: " .. self.age)
end
person = Person:new({ name = "ray", age = 12 })
person2 = Person:new({ name = "hh", age = 13 })
person:show()
person2:show()
-->> name:ray, age: 12
-->> name:hh, age: 13
在本例中清女,當(dāng)創(chuàng)建一個(gè)對(duì)象時(shí)钱烟,person=Person:new
,在該方法中嫡丙,設(shè)置了self
為其元表(setmetatable(p, self)
)拴袭,即person的元表為Person
;因此當(dāng)調(diào)用person:show()
時(shí)曙博,其實(shí)際調(diào)用為person.show(person)
拥刻,查找索引時(shí)會(huì)先從person的table中查找,未找到父泳,則查找__index
條目般哼,上例中設(shè)置了self
的__index
為self本身吴汪,此時(shí)__index
的元表也是Person
,那么此時(shí)的調(diào)用為Person.show(person)
蒸眠,找到show
方法并執(zhí)行漾橙。
將類的定義抽象,并劃定步驟黔宛,那么Lua在創(chuàng)建一個(gè)類時(shí)近刘,只需要兩步:
- 創(chuàng)建一個(gè)基礎(chǔ)原型table
- 創(chuàng)建一個(gè)實(shí)例化方法,并設(shè)置關(guān)聯(lián)元表以及__index
- 其他的方法定義均為table:functionName
A = {} -- 可具有默認(rèn)數(shù)據(jù)
function A:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
類定義完成后臀晃,在訪問(wèn)屬性和方法時(shí)觉渴,
.
訪問(wèn)屬性,如A.b
徽惋,:
訪問(wèn)方法案淋,如A:function()
繼承
在面向?qū)ο缶幊讨校^承是另外一個(gè)非常重要的方面险绘,比如當(dāng)我們要定義各個(gè)品牌的汽車時(shí)踢京,哈弗、吉利宦棺、奇瑞等瓣距,汽車都是四個(gè)轱轆、四個(gè)門(mén)等代咸,我們自然會(huì)想到蹈丸,需要抽象出一個(gè)基礎(chǔ)類,讓其他品牌汽車都繼承基礎(chǔ)類呐芥。
以下步驟實(shí)現(xiàn)一個(gè)基礎(chǔ)的汽車對(duì)象逻杖,定義了四個(gè)車輪、四個(gè)車門(mén)和一個(gè)原型的方向盤(pán)思瘟,并提供了一個(gè)打印汽車基礎(chǔ)信息的方法:
Car = { wheel = 4, door = 4, steeringWheel = "circular" }
function Car:new(c)
c = c or {}
setmetatable(c, self)
self.__index = self
return c
end
function Car:showCarInfo()
print("車輪:" .. self.wheel .. "荸百,車門(mén):" .. self.door .. ",方向盤(pán)形狀:" .. self.steeringWheel)
end
基礎(chǔ)類定義完成后滨攻,我們將重新定義一個(gè)新的跑車類够话,讓跑車集成汽車類,第一步铡买,先使用汽車類創(chuàng)建出一個(gè)默認(rèn)對(duì)象更鲁,并讓跑車指向該對(duì)象,此時(shí)奇钞,跑車將和轎車類具備一模一樣的方法澡为。
SportCar = Car:new()
s = SportCar:new()
s:showCarInfo()
-->> 車輪:4,車門(mén):4景埃,方向盤(pán)形狀:circular
如果只是改變汽車的基礎(chǔ)屬性媒至,或者是新增加新的屬性顶别,則可以直接使用new方法傳遞對(duì)象的方式實(shí)現(xiàn)即可,并不需要新增代碼拒啰,如跑車的車門(mén)數(shù)量為2驯绎,此時(shí)仍然使用基類的創(chuàng)建方法即可完成。
SportCar = Car:new()
s = SportCar:new { door = 2 }
s:showCarInfo()
-->> 車輪:4谋旦,車門(mén):2剩失,方向盤(pán)形狀:circular
為了更清晰的明確繼承和新類的定義方法,可以重寫(xiě)new函數(shù)册着,如下:
SportCar = Car:new()
function SportCar:new(s)
s = s or Car:new(s)
setmetatable(s, self)
self.__index = self
return s
end
s = SportCar:new { door = 2, steeringWheel = "Hexagon" } -- 方向盤(pán)為更酷的六邊形
s:showCarInfo()
-->> 車輪:4拴孤,車門(mén):2,方向盤(pán)形狀:Hexagon
實(shí)現(xiàn)基礎(chǔ)的對(duì)象繼承后甲捏,可以對(duì)新的跑車演熟,添加額外的方法,比如跑車的最高時(shí)速可達(dá)200公里司顿。
function SportCar:getMaxSpeed()
return self.maxSpeed .. "公里"
end
s = SportCar:new { door = 2, steeringWheel = "Hexagon", maxSpeed = 200 }
print(s:getMaxSpeed())
-->> 200公里
面向?qū)ο缶幊讨忻⒋猓哂兄貙?xiě)方法的概念,對(duì)于實(shí)現(xiàn)了集成的Lua對(duì)象來(lái)說(shuō)大溜,也具備該功能化漆。我們實(shí)現(xiàn)了跑車類后,新增最高時(shí)速钦奋,那么基類中的展示汽車的基礎(chǔ)屬性方法顯然無(wú)法滿足我們的需求获三,此時(shí)可以重寫(xiě)該方法。
function SportCar:showCarInfo()
print("跑車:車輪:" .. self.wheel .. "锨苏,車門(mén):" .. self.door .. ",方向盤(pán)形狀:" .. self.steeringWheel .. "棺聊,最高時(shí)速:" .. self:getMaxSpeed())
end
s = SportCar:new { door = 2, steeringWheel = "Hexagon", maxSpeed = 200 }
s:showCarInfo()
-->> 跑車:車輪:4伞租,車門(mén):2,方向盤(pán)形狀:Hexagon限佩,最高時(shí)速:200公里
至此類的繼承已經(jīng)實(shí)現(xiàn)完成葵诈,將上述散亂的代碼合并在一起,如下:
Car = { wheel = 4, door = 4, steeringWheel = "circular" }
function Car:new(c)
c = c or {}
setmetatable(c, self)
self.__index = self
return c
end
function Car:showCarInfo()
print("車輪:" .. self.wheel .. "祟同,車門(mén):" .. self.door .. "作喘,方向盤(pán)形狀:" .. self.steeringWheel)
end
SportCar = Car:new()
function SportCar:new(s)
s = s or Car:new(s)
setmetatable(s, self)
self.__index = self
return s
end
function SportCar:getMaxSpeed()
return self.maxSpeed .. "公里"
end
function SportCar:showCarInfo()
print("跑車:車輪:" .. self.wheel .. ",車門(mén):" .. self.door .. "晕城,方向盤(pán)形狀:" .. self.steeringWheel .. "泞坦,最高時(shí)速:" .. self:getMaxSpeed())
end
c = Car:new()
s = SportCar:new { door = 2, steeringWheel = "Hexagon", maxSpeed = 200 }
c:showCarInfo()
s:showCarInfo()
-->> 車輪:4,車門(mén):4砖顷,方向盤(pán)形狀:circular
-->> 跑車:車輪:4贰锁,車門(mén):2赃梧,方向盤(pán)形狀:Hexagon,最高時(shí)速:200公里
訪問(wèn)限制
訪問(wèn)限制是面向?qū)ο蟮牧硗庖粋€(gè)方面豌熄,對(duì)于Java來(lái)說(shuō)授嘀,可以通過(guò)private
、protected
锣险、public
很容易實(shí)現(xiàn)訪問(wèn)權(quán)限控制蹄皱,而對(duì)于Lua來(lái)說(shuō),類都是不具備的芯肤,私密控制同樣沒(méi)有巷折;Lua是使用table進(jìn)行的模擬實(shí)現(xiàn)類,那么和Lua閉包相結(jié)合纷妆,也可以實(shí)現(xiàn)私密訪問(wèn)盔几。
function Car()
local _M = {
wheel = 4, door = 4, steeringWheel = "circular"
}
function _M:new(c)
c = c or {}
setmetatable(c, self)
for k, v in pairs(self) do
if not o[k] then
o[k] = v
end
end
self.__index = self
return c
end
local function run()
print("普通轎車,100公里每小時(shí)速度進(jìn)行行駛")
end
function _M:showCarInfo()
print("車輪:" .. self.wheel .. "掩幢,車門(mén):" .. self.door .. "逊拍,方向盤(pán)形狀:" .. self.steeringWheel)
run()
end
return _M
end
c = Car()
c:showCarInfo()
c.run() -- 外部無(wú)法使用
-->> 車輪:4,車門(mén):4际邻,方向盤(pán)形狀:circular
-->> 普通轎車芯丧,100公里每小時(shí)速度進(jìn)行行駛
這種方式的實(shí)現(xiàn)原理是采用了兩個(gè)元表,公開(kāi)的方法世曾,都放入到_M
元表中缨恒,并于最后返回,不公開(kāi)的方法轮听,都存儲(chǔ)在本身元表中骗露。
一般情況下,對(duì)于模塊(類)的定義可以固化為如下形式
local _M = {
_VERSION = "1.0",
_NAME = "Http 方法封裝"
}
-- 1. 私有方法放置在這里
local function joinParam(param)
local str = ""
for i, v in pairs(param) do
if str ~= "" then
str = str .. "&"
end
str = str .. i .. "=" .. v
end
return str
end
local function request(url, param, method)
return "向" .. url .. "發(fā)起" .. method .. "方法血巍,傳遞參數(shù):" .. joinParam(param)
end
-- 2. new方法
function _M:new()
local o = o or {}
setmetatable(o, self)
for k, v in pairs(self) do
if not o[k] then
o[k] = v
end
end
self.__index = self
return o
end
-- 3. 公開(kāi)的方法
function _M:get(url, param)
return request(url, param, "GET")
end
-- 4. 返回_M對(duì)象
return _M
在其他類中引用該對(duì)象發(fā)起http請(qǐng)求
-- testHttp為上述類的文件名萧锉,如果有路徑也需要定義,如path.fileName
local http = require("testHttp"):new()
local content = http:get("http://baidu.com", { uid = "ray", pwd = "111111" })
print(content)
-->> 向http://baidu.com發(fā)起GET方法述寡,傳遞參數(shù):uid=ray&pwd=111111