Lua面向?qū)ο?/h1>

Lua的設(shè)計初衷并非意圖構(gòu)建完整的應(yīng)用,而是在應(yīng)用程序中為應(yīng)用提供靈活的擴(kuò)展和定制功能,所以Lua僅提供了基礎(chǔ)的數(shù)學(xué)運算和字符串處理等函數(shù)庫,而并未涵蓋程序設(shè)計的各個方面。

Lua作為腳本語言甚至沒有原生態(tài)的提供面向?qū)ο蟮闹С郑鷳B(tài)的Lua中是沒有類這個概念的。不過Lua提供的table表這個強(qiáng)大的數(shù)據(jù)結(jié)構(gòu)卻賦予開發(fā)者自行實現(xiàn)面向?qū)ο笠饬x上的類的能力。

在Lua中,使用表和函數(shù)實現(xiàn)面向?qū)ο螅瑢⒑瘮?shù)和相關(guān)數(shù)據(jù)放置于同一個表中就形成了一個對象。

-- 銀行賬戶 account.lua
local _M = {}
-- 元表
local mt = { __index = _M }
-- 新建賬戶,初始化賬戶余額
function _M:new(balance)
    balance = balance or 0
    -- setmetable將_M作為新建表的原型
    return setmetatable( {balance = balance},  mt )
end
-- 存款
function _M:deposit(v)
    self.balance = self.balance + v
end
-- 取款
function _M:withdraw(v)
    if self.balance>v then
        self.balance = self.balance  - v
    else
        error("insufficient funds")
    end
end
-- 返回
return _M
-- test.lua
local Account = require("account")

local account1 = Account:new()
account1:deposit(100) -- 100
print( account1.balance )

account1:withdraw(10) -- 90
print( account1.balance )

local account2 = Account:new()
account2:deposit(50)
print( account2.balance ) -- 50

類與對象

  • 對象是真實世界中的實體,對象與實體是一一對應(yīng)的,也就是說現(xiàn)實世界中每個實體都是一個對象,它是一種具體的概念。
  • 類是具備某些共同特征的實體的集合,是一種抽象的概念。

Lua中table就是一種對象,這句話從3個方面可以得到證實:

  • 表和對象一樣可以擁有狀態(tài)
  • 表和對象一樣擁有一個獨立于其值的標(biāo)識
  • 表和對象一樣具有獨立于創(chuàng)建者和創(chuàng)建地的生命周期
-- 創(chuàng)建對象
local Account = {
    balance = 0
}
-- 創(chuàng)建函數(shù)并將其存入對象的字段中
function Account.withdraw(val)
    Account.balance = Account.balance - val
end
-- 使函數(shù)只針對特定對象工作
function Account:disposit(val)
    self.balance = self.balance + val
end

一個類就像是一個創(chuàng)建對象的模具,由于Lua中沒有類的概念,每個對象只能自定義行為和形態(tài)。Lua中模擬類可參照基于原型的語言,每個對象有有一個原型(prototype)。原型也是一種對象,當(dāng)其他對象遇到未知操作時,原型會像查找它。因此,要表示一個類,只需要創(chuàng)建一個專門用來作為其他對象的原型。類和原型都是一種組織對象間共享行為的方式。簡單來說,Lua本身沒有類的概念,只有table表,而面向?qū)ο蟮膶崿F(xiàn)只不過是將表與父類的表連在一起,沒有某個變量時就去父類查找。

需求:銀行賬戶(Account)可存款(deposit)和取款(withdraw),取款時當(dāng)前賬戶(balance)余額不足則提示用戶。

-- 創(chuàng)建類
local Account = {
    balance = 0
}
-- 實例化通過類創(chuàng)建對象
function Account:new(tbl)
    -- 默認(rèn)初始化為空表
    local tbl = tbl or {}
    -- 設(shè)置自身作為元表
    setmetatable(tbl, self)
    -- 新對象繼承元表中的行為
    self.__index = self
    return tbl
end
-- 創(chuàng)建函數(shù)并將其存入對象的字段中
function Account:withdraw(val)
    -- self:使函數(shù)只針對特定對象工作
    if val > self.balance then
        error "insufficient funds"
    end
    self.balance = self.balance - val
end
function Account:disposit(val)
    self.balance = self.balance + val
end

-- test
local obj = Account:new{balance = 10}
obj:disposit(100)
print(obj.balance)
obj:withdraw(20)
print(obj.balance)

實現(xiàn)Lua面向?qū)ο罂煞纸鉃轭惖亩x和類的實例化兩個問題,類的定義主要是實現(xiàn)繼承,即怎么樣子類擁有父類的方法集。類的實例化需要解決實例如何共享類的方法集,但獨享自己的成員變量實例。

Cocos2dx-lua中有一個class函數(shù)實現(xiàn)了類的基礎(chǔ),包括單繼承和多重繼承。Cocos2dx-lua中class的實現(xiàn)中,子類在定義時復(fù)制所有基類的方法,在實例化時將該類作為metatable__index賦值給實例。

-- 類
-- classname 類名
-- ... 父類 可選參數(shù) 類型為table或function
function class(classname, ...)

    local cls = {__cname = classname}
    local supers = {...} --父類
    -- 遍歷父類
    for _,super in ipairs(supers) do
        -- 判斷父類類型
        local superType = type(super)
        assert(
            superType=='nil' or superType=='table' or superType=='function',
            string.format("create class %s width invalid super class type %s", classname, superType)
        )
        if superType == 'function' then
            assert(cls.__create==nil, string.format("create class %s with more than one creating function"), classname)
            -- 若父類為函數(shù)則讓cls的create方法指向它
            cls.__create = super
        elseif superType == 'table' then
            if super[".isclass"] then
                assert(cls.__create==nil, string.format("create class %s with more than one creating function", classname))
                -- 若父類是原生cocos類
                cls.__create = function()
                    super:create()
                end
            else
                -- 若父類為自定義類
                cls.__supers = cls.__supers or {}
                -- 保存cls的多個父類的table表
                cls.__supers[#cls.__supers+1] = super
                -- 將遍歷到第一個table作為cls的父類
                if not cls.super then
                    cls.super = super
                end
            end
        else
            -- 若父類既不是table也不是function則報錯
            -- 若父類不存在則不會執(zhí)行到此處,因此父類可以為nil。
            error(string.format("create class %s with invalid super type", classname), 0)
        end
    end
    -- 設(shè)置類,設(shè)置類的第一索引對象為自己,若實例對象找不到某參數(shù)的話,會查找該類是否包含該參數(shù),若不包含則向父類查找。
    cls.__index = cls
    if not cls.__supers or #cls.__supers==1 then
        -- 若類僅有一個父類即單繼承則設(shè)置cls的父類為其元表
        setmetatable(cls, {__index=cls.super})
    else
        -- 若類為多繼承,其索引是一個函數(shù),查找元素時會執(zhí)行該函數(shù)。
        setmetatable(cls, {__index=function(_, key) 
            local supers = cls.__supers
            for i=1,#supers do
                local super = supers[i]
                if super[key] then
                    return super[key]
                end
            end
        end})
    end
    -- 判斷類的構(gòu)造器
    if not cls.ctor then
        cls.ctor = function() end
    end
    -- 類實例化方法
    cls.new = function(...)
        local instance
        -- 判斷類是否存在create方法若存在則調(diào)用,若不存在則為普通類。
        if cls.__create then
            -- 存在create方法的類通過__index和元表的index,一級一級向上查找,直到原生cocos類。
            instance = cls.__create(...)
        else
            -- 普通類即自定義的類時無create方法的
            instance = {}
        end
        -- 設(shè)置實例的元表索引,誰調(diào)用了new就將其設(shè)置為實例的元表索引
        setmetatableindex(instance, cls)
        instance.class = cls
        -- 實例調(diào)用構(gòu)造器
        instance:ctor(...)
        -- 返回實例
        return instance
    end
    -- 創(chuàng)建類的實例
    cls.create = function(_, ...)
        return cls.new(...)
    end
    -- 返回類
    return cls
end

-- 設(shè)置實例的元表索引
setmetatableindex = function(table, index)
    if type(table)=='userdata' then
        local peer = tolua.getpeer(table)
        if not peer then
            peer = {}
            tolua.setpeer(table, peer)
        end
        setmetatableindex(peer, index)
    else
        local metatable = getmetatable(table)
        if not metatable then metatable = {} end
        if not metatable.__index then
            metatable.__index = index
            setmetatable(table, metatable)
        elseif metatable.__index ~= index then
            setmetatableindex(metatable, index)
        end
    end
end

繼承

繼承可用元表實現(xiàn),它提供了在父類中查找存在的方法和變量的機(jī)制。在Lua中不推薦使用繼承方式完成構(gòu)造的,這樣做引入的問題可能比解決的問題要多。

-- str_base.lua
local _M = {}
local mt = {__index = _M}

function _M.upper(str)
    return string.upper(str)
end

return _M
-- str_extend.lua
local StrBase = require("str_base")

local _M = {}
-- 使用元表實現(xiàn)繼承,由于元表提供在父類中查找存在的方法和變量的機(jī)制。
_M = setmetatable(_M, {__index = StrBase})

function _M.lower(str)
    return string.lower(str)
end

return _M
-- test.lua
local StrExtend = require("str_extend")
print( StrExtend.upper("Hello"), StrExtend.lower("Hello") )

由于類也是對象,類可從其他類中獲得方法,這種行為就是一種繼承。

需求:設(shè)置特殊賬戶并限制透支

-- 創(chuàng)建類
local Account = {
    balance = 0
}
-- 實例化通過類創(chuàng)建對象
function Account:new(tbl)
    -- 默認(rèn)初始化為空表
    local tbl = tbl or {}
    -- 設(shè)置自身作為元表
    setmetatable(tbl, self)
    -- 新對象繼承元表中的行為
    self.__index = self
    return tbl
end
-- 創(chuàng)建函數(shù)并將其存入對象的字段中
function Account:withdraw(val)
    -- self:使函數(shù)只針對特定對象工作
    if val > self.balance then
        error "insufficient funds"
    end
    self.balance = self.balance - val
end
function Account:disposit(val)
    self.balance = self.balance + val
end

-- 繼承,從類中派生子類
local SpecialAccount = Account:new()
-- 子類覆寫父類方法
function SpecialAccount:withdraw(val)
    if val-self.balance >= self:getLimit() then
        error "insufficient funds"
    end
    self.balance = self.balance - val
end
function SpecialAccount:getLimit()
    return self.limit or 0
end

-- test
local obj = SpecialAccount:new{limit = 50}
obj:withdraw(10)
print(obj.balance, obj.limit)

成員私有性

Lua中的對象設(shè)計不提供私有性訪問機(jī)制,部分原因是使用通用數(shù)據(jù)結(jié)構(gòu)table來表示對象的結(jié)果。Lua并沒有打算被用來進(jìn)行大型程序設(shè)計,相反,Lua的目標(biāo)定于小型或中型的編程設(shè)計,通常是作為大型系統(tǒng)的一部分。Lua避免太冗余和太多的人為限制。如果你不想訪問一個對象內(nèi)的東西就不要訪問。

If you do not want to access something inside an object, just do not do it.

然后,Lua的另一個目標(biāo)是靈活性,提供程序員元機(jī)制(meta-mechanisms),通過它你可以實現(xiàn)很多不同的機(jī)制。雖然Lua中基本的面向?qū)ο笤O(shè)計并不提供私有性訪問的機(jī)制,仍可用不同的方式來實現(xiàn)他。

設(shè)計的基本思想是:每個對象用2個表來表示,一個描述狀態(tài),一個描述操作(接口)。對象本身通過第2個表來訪問,也就是說,通過接口來訪問對象。為了避免未授權(quán)的訪問,表示狀態(tài)的表中不涉及到操作,表示操作的表也不涉及到狀態(tài)。取而代之的是狀態(tài)被保存在方法的閉包內(nèi)。

另外,在動態(tài)語言中引入成員私有性并沒有太大的必要,反而會顯著增加運行時的開銷,畢竟這種檢查無法像許多靜態(tài)語言那樣在編譯期完成。

在Lua中,成員的私有性,使用類似于函數(shù)閉包的形式來實現(xiàn)。將對象作為方法的upvalue,本身是很巧妙的,但會讓子類繼承變得困難,同時構(gòu)造函數(shù)動態(tài)創(chuàng)建了函數(shù),會導(dǎo)致構(gòu)造函數(shù)無法被JIT編譯。

例如:使用工廠方法創(chuàng)建新的賬戶實例,通過工廠方法對外提供的閉包來暴露對外接口,而不想暴露在外的如balance成員變量,則被很好的隱藏起來。

-- 使用工廠方法創(chuàng)建賬戶實例
function AccountInstance(initBalance)
    local self = {
        balance = initBalance
    }
    -- 使用閉包實現(xiàn)成員的私有性
    local getBalance = function()
        return self.balance
    end
    local deposit = function(v)
        self.balance = self.balance + v
    end
    local withdraw = function(v)
        self.balance = self.balance - v
    end
    return {
        getBalance = getBalance,
        deposit = deposit,
        withdraw = withdraw
    }
end

local account =  AccountInstance(100)
account.deposit(10)
print( account.getBalance(), account.balance ) -- 110 nil

account.withdraw(10)
print( account.getBalance(), account.balance ) -- 100 nil

首先,函數(shù)創(chuàng)建一個表用來描述對象的內(nèi)部狀態(tài),并保存在局部變量self內(nèi)。然后,函數(shù)為對象的每個方法創(chuàng)建閉包,也就是說,嵌套的函數(shù)實例。最后,函數(shù)創(chuàng)建并返回外部對象,外部對象中將局部變量名指向最終要實現(xiàn)的方法。這兒的關(guān)鍵點在于:這些方法沒有使用額外的參數(shù)self,代替的是直接訪問self。因為沒有這個額外的參數(shù),因此不能使用冒號語法來訪問這些對象,含住只能像其它函數(shù)一樣調(diào)用。

這種設(shè)計實現(xiàn)了任何存儲在self表中的部分都是私有的,工廠方法返回實例后,沒有什么方法可以直接訪問對象,只能通過工廠函數(shù)中定義的函數(shù)來訪問。

例如:給某些取款用戶享有額外的10%的存款上限

function AccountInstance(initBalance)
    local self = {
        balance = initBalance,
        limit = 1314
    }
    local withdraw = function(v)
        self.balance = self.balance - v
    end
    local deposit = function(v)
        self.balance = self.balance + v
    end
    local extra = function()
        if self.balance > self.limit then
            return self.balance * 0.01
        else
            return 0
        end
    end
    local balance = function()
        return self.balance + extra()
    end
    return {
        withdraw = withdraw,
        deposit = deposit,
        balance = balance
    }
end

local account = AccountInstance(100)
account.deposit(2000)
print(account.balance()) --2121

這樣,對于用戶而言就沒有辦法直接訪問extra函數(shù),也就實現(xiàn)了Lua Private Function

使用table來實現(xiàn)面向?qū)ο蟮木幊谭绞剑瑤缀蹩梢詫崿F(xiàn)所有面向?qū)ο蟮木幊烫匦裕鼪]有也不想去實現(xiàn)的就是對象的私密性,也就是C++里面的privatepublicprotected,這與Lua的設(shè)計初衷有關(guān),Lua定位于小型程序開發(fā),參與一個工程的人不會很多,自行約束非要實現(xiàn)私密性的話Lua也不是可行,只是不能再使用table和元表的方式了。

可使用上述方式實現(xiàn),在閉包中定義一個tableupvalue,然后把閉包函數(shù)都定義在這個table中,然后返回這個table,使用key訪問內(nèi)部方法。使用閉包實現(xiàn)對象的方式比使用table效率高并實現(xiàn)了絕對的私密性,但無法實現(xiàn)繼承,相當(dāng)于簡單的小對象,甚至可以在閉包中僅定義一個方法,然后通過key來判斷調(diào)用的是什么方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 13,878評論 0 38
  • 君有疾否by入似我聞 文案 “世譽,我心不假。”楚明允將手隱入袖中掐了自己一把,言辭深情。 蘇世譽的笑容忽然深了,...
    我叫隔壁老王閱讀 286評論 0 0
  • 永遇樂 蘇軾 明月如霜,好風(fēng)如水,清景無限。 曲港跳魚,圓荷瀉露,寂寞無人見。 紞如三鼓,錚然一葉,黯黯夢云驚...
    顧勇詩書閱讀 294評論 0 4
  • 在和麗表姐的談話中我知道燕表姐竟然也懷孕三個多月了。 你們這是做什么?準(zhǔn)備湊一塊做月子嗎? 麗表姐笑笑:那當(dāng)然了,...
    喜暮雨閱讀 365評論 1 2
  • 今天從網(wǎng)上看到一篇的文章,名字就叫《你想過自己會孤獨終老嗎》文章主旨是質(zhì)疑一個奔三的朋友找了一個不怎么喜歡也不怎么...
    哈哈哈max閱讀 614評論 0 2