Lua極簡入門(十)——面向?qū)ο?/h2>

在介紹完Lua的基礎(chǔ)知識包括元表,函數(shù)式編程之后,終于到了Lua面向?qū)ο缶幊?。雖然并不打算使用Lua進(jìn)行大型應(yīng)用系統(tǒng)(程序)的開發(fā),最多可能是嵌入到某個系統(tǒng)之間,如在Redis中使用Lua腳本完成一些操作,或者使用Nginx+Lua完成服務(wù)限流或者日志收集,負(fù)載均衡;另外比如我這里目前計劃使用Lua+Angular進(jìn)行一個Web前端的項目開發(fā);這些工作仍然繞不開面向?qū)ο蟆?/p>

面向?qū)ο缶幊蘋OP(Object Oriented Programming),作為一種編程思想隨著互聯(lián)網(wǎng)的發(fā)展,已經(jīng)深入到現(xiàn)代系統(tǒng)編程的方方面面;OOP將對象作為程序的基礎(chǔ)單元,包含了數(shù)據(jù)和操作函數(shù);面向?qū)ο蟪绦蛟O(shè)計時,將業(yè)務(wù)中的事務(wù)進(jìn)行抽象封裝成多個對象,程序在執(zhí)行時,數(shù)據(jù)和消息將在每個對象間流轉(zhuǎn),并按一定的操作流程依次執(zhí)行。

類和實例是面向?qū)ο蟮闹匾拍?,對于Java、C#等面向?qū)ο笳Z言來說,一般都具有關(guān)鍵詞class來標(biāo)識和定義一個類,并組織類模板;對于Lua來說,并不具備類的概念,table是Lua的最基礎(chǔ)對象,借助于tableLua也可以模擬類,但每個對象需要自己定義行為和狀態(tài)。

Person對象為例,借助table很容易實現(xiàn)一個類,這個類具有兩個屬性,和一個方法。

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

在這段代碼中,看似實現(xiàn)了一個類,但其實只是對方法的一個封裝,無法從該類創(chuàng)建不同的實例,類只是模板,使用類模板,可以創(chuàng)建出不同的實例,是面向?qū)ο蟮闹饕卣鳌H缟鲜鍪纠?,如果按照J(rèn)ava的方法聲明對象:

person = Person -- 按照Lua的特點(diǎn),只是將person指向了Person,并沒有聲明實例
Person = nil    -- Person消亡,person也消亡
person.show("ray", 12)  -- 異常,說明這并不是類,只是一個方法,只有一個對象的聲明周期
-->> attempt to index a nil value (global 'Person')

這個例子說明按這種方式,只是定義了一個方法。類是對事物的一種抽象,如Person應(yīng)該是對的一種抽象,而應(yīng)用該類模板,可以聲明ray等等實例,其描述一個現(xiàn)實具體的人,按這種方式理解,當(dāng)生命多個實例時,每個實例的聲明周期都是獨(dú)立的,并不相互影響。Java中使用this來描述當(dāng)前實例,Lua中可以使用self作為接收者,描述當(dāng)前實例對象。

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

每個方法都放置self參數(shù)太麻煩了,Lua也可以像Java一樣,編碼時對this實現(xiàn)隱藏,Lua可以隱藏self參數(shù),實現(xiàn)在編碼時不必顯式聲明self。Lua在聲明時,使用:達(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

使用:只是簡化了顯式self參數(shù)的傳入,包括調(diào)用和聲明時的傳遞,其他的和傳入self功能一致。如,聲明時使用:,調(diào)用時,使用.并傳入self效果一致。

-- 上例最后一步
person.show(person)
-->> ray    12

到這里L(fēng)ua使用table解決了類的獨(dú)立生命周期、隱藏self的問題,但是目前編寫的對象讓然不能稱之為,最基礎(chǔ)的,沒有辦法從上述定義中,獨(dú)立聲明多個實例。比如上例聲明了person對象后,將無法再次聲明第二個實例。

person = Person
Person:setInfo("ray", 12)
person2 = Person
person2:setInfo("hh", 13)
person:show()
person2:show()
-->> name:hh, age: 13
-->> name:hh, age: 13

對于Java來說,類就是個抽象事物的模板,使用new關(guān)鍵詞,可以創(chuàng)建任意的實例,每一個實例都是具有模板中抽象的事務(wù)的獨(dú)立對象。Lua由于沒有類的概念,使用table模擬類時,如上例,聲明的對象將是同一個對象,這和類的表現(xiàn)不一致。為了解決獨(dú)立實例的問題,只能自己定義類的形態(tài)和行為。

元表一章中,介紹過不同原型實現(xiàn)集成的功能,使用setmetatable、__index進(jìn)行元表設(shè)置,可以很容易的實現(xiàn)一個原型從另一個原型繼承。

當(dāng)訪問一個table中的字段時,Lua會先從table中查找該字段,如果存在,則返回該字段的值;如果沒有,則檢查該table是否具有元表,如果沒有元表,則返回nil;如果有元表,則會從元表中查找__index元方法,如果沒有該元方法,返回nil;如果有__index元方法,則從該方法中查找指定字段。__index方法可以返回一個函數(shù)、也可以返回一個table

仍然使用上述示例,使用元表編程的方式,對這個Person對象進(jìn)行修改,提供一個類似Java的new實例的方法,當(dāng)創(chuàng)建一個新的對象時,將該對象繼承Person的所有對象及方法,通過setmetatable讓新對象的原型指向self,并設(shè)置__index索引也指向self。

Person = {}
function Person:new(p)
    -- 初始化,防止p(table)為空
    p = p or {}
    -- sefl為p的原型
    setmetatable(p, self)
    self.__index = self
    -- 返回創(chuàng)建的實例,此時p將具備Person的所有對象
    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)建一個對象時,person=Person:new,在該方法中,設(shè)置了self為其元表(setmetatable(p, self)),即person的元表為Person;因此當(dāng)調(diào)用person:show()時,其實際調(diào)用為person.show(person),查找索引時會先從person的table中查找,未找到,則查找__index條目,上例中設(shè)置了self__index為self本身,此時__index的元表也是Person,那么此時的調(diào)用為Person.show(person),找到show方法并執(zhí)行。

將類的定義抽象,并劃定步驟,那么Lua在創(chuàng)建一個類時,只需要兩步:

  • 創(chuàng)建一個基礎(chǔ)原型table
  • 創(chuàng)建一個實例化方法,并設(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

類定義完成后,在訪問屬性和方法時,.訪問屬性,如A.b,:訪問方法,如A:function()

繼承

在面向?qū)ο缶幊讨?,繼承是另外一個非常重要的方面,比如當(dāng)我們要定義各個品牌的汽車時,哈弗、吉利、奇瑞等,汽車都是四個轱轆、四個門等,我們自然會想到,需要抽象出一個基礎(chǔ)類,讓其他品牌汽車都繼承基礎(chǔ)類。

以下步驟實現(xiàn)一個基礎(chǔ)的汽車對象,定義了四個車輪、四個車門和一個原型的方向盤,并提供了一個打印汽車基礎(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 .. ",車門:" .. self.door .. ",方向盤形狀:" .. self.steeringWheel)
end

基礎(chǔ)類定義完成后,我們將重新定義一個新的跑車類,讓跑車集成汽車類,第一步,先使用汽車類創(chuàng)建出一個默認(rèn)對象,并讓跑車指向該對象,此時,跑車將和轎車類具備一模一樣的方法。

SportCar = Car:new()

s = SportCar:new()
s:showCarInfo()
-->> 車輪:4,車門:4,方向盤形狀:circular

如果只是改變汽車的基礎(chǔ)屬性,或者是新增加新的屬性,則可以直接使用new方法傳遞對象的方式實現(xiàn)即可,并不需要新增代碼,如跑車的車門數(shù)量為2,此時仍然使用基類的創(chuàng)建方法即可完成。

SportCar = Car:new()

s = SportCar:new { door = 2 }
s:showCarInfo()
-->> 車輪:4,車門:2,方向盤形狀:circular

為了更清晰的明確繼承和新類的定義方法,可以重寫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" } -- 方向盤為更酷的六邊形
s:showCarInfo()
-->> 車輪:4,車門:2,方向盤形狀:Hexagon

實現(xiàn)基礎(chǔ)的對象繼承后,可以對新的跑車,添加額外的方法,比如跑車的最高時速可達(dá)200公里。

function SportCar:getMaxSpeed()
    return self.maxSpeed .. "公里"
end

s = SportCar:new { door = 2, steeringWheel = "Hexagon", maxSpeed = 200 }
print(s:getMaxSpeed())
-->> 200公里

面向?qū)ο缶幊讨?,具有重寫方法的概念,對于實現(xiàn)了集成的Lua對象來說,也具備該功能。我們實現(xiàn)了跑車類后,新增最高時速,那么基類中的展示汽車的基礎(chǔ)屬性方法顯然無法滿足我們的需求,此時可以重寫該方法。

function SportCar:showCarInfo()
    print("跑車:車輪:" .. self.wheel .. ",車門:" .. self.door .. ",方向盤形狀:" .. self.steeringWheel .. ",最高時速:" .. self:getMaxSpeed())
end

s = SportCar:new { door = 2, steeringWheel = "Hexagon", maxSpeed = 200 }
s:showCarInfo()
-->> 跑車:車輪:4,車門:2,方向盤形狀:Hexagon,最高時速:200公里

至此類的繼承已經(jīng)實現(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 .. ",車門:" .. self.door .. ",方向盤形狀:" .. 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 .. ",車門:" .. self.door .. ",方向盤形狀:" .. self.steeringWheel .. ",最高時速:" .. self:getMaxSpeed())
end

c = Car:new()
s = SportCar:new { door = 2, steeringWheel = "Hexagon", maxSpeed = 200 }
c:showCarInfo()
s:showCarInfo()
-->> 車輪:4,車門:4,方向盤形狀:circular
-->> 跑車:車輪:4,車門:2,方向盤形狀:Hexagon,最高時速:200公里

訪問限制

訪問限制是面向?qū)ο蟮牧硗庖粋€方面,對于Java來說,可以通過private、protected、public很容易實現(xiàn)訪問權(quán)限控制,而對于Lua來說,類都是不具備的,私密控制同樣沒有;Lua是使用table進(jìn)行的模擬實現(xiàn)類,那么和Lua閉包相結(jié)合,也可以實現(xià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公里每小時速度進(jìn)行行駛")
    end
    function _M:showCarInfo()
        print("車輪:" .. self.wheel .. ",車門:" .. self.door .. ",方向盤形狀:" .. self.steeringWheel)
        run()
    end
    return _M
end

c = Car()
c:showCarInfo()
c.run() -- 外部無法使用
-->> 車輪:4,車門:4,方向盤形狀:circular
-->> 普通轎車,100公里每小時速度進(jìn)行行駛

這種方式的實現(xiàn)原理是采用了兩個元表,公開的方法,都放入到_M元表中,并于最后返回,不公開的方法,都存儲在本身元表中。

一般情況下,對于模塊(類)的定義可以固化為如下形式

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. 公開的方法
function _M:get(url, param)
    return request(url, param, "GET")
end

-- 4. 返回_M對象
return _M

在其他類中引用該對象發(fā)起http請求

-- 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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容