lua實現面向對象(一)


c++和java語言機制中本身帶有面向對象的內容,而lua設計的思想是元編程,沒有面向對象的實現。

但是利用lua的元表(matetable)機制,可以實現面向對象。要講清楚怎樣實現lua面向對象,需要講清楚以下內容。

1.lua元表 2.類和對象 3.繼承

 

1.lua元表

lua里的所有數據結構都是表。metatable可以改變table的行為。例如加法行為,table本身沒有加法行為。

可以通過修改元表中的__add域,來提供表的加法行為。__add叫做元方法(metamethod)。

一個表默認是不帶元表的。getmetatable可以獲取元表。

t = {}
print(getmetatable(t)) --nil

setmetatable可以設置元表。

t = {}
setmetatable(t, t) --設置自己是自己的元表
print(t == getmetatable(t)) --true

任何一個表可以其他表的元表。一個表也可以是自己的元表。

來看一個展示__add元方法的例子。實現一個集合的並集運算。

Set = {}

Set.mt = {}

Set.new = function (t)
    local res = {}
    setmetatable(res, Set.mt)
    for k, v in pairs(t) do    --用原始集合的value作為新集合的key
        res[v] = true            --新集合的value值 可以是任意值
    end
    return res
end

Set.union = function (a, b)
    local res = Set.new{}    --res將Set.mt作為元表
    for k, v in pairs(a) do res[k] = true end    --res的所有key值作為新的並集,value值可以是任意值
    for k, v in pairs(b) do res[k] = true end
    return res
end

Set.mt.__add = Set.union

Set.print = function (t)
    local s = "{ "
    for k, v in pairs(t) do
        s = s .. tostring(k) .. " "    --所有key值作為新的並集
    end
    s = s .. "}"
    print(s)
end

s1 = Set.new{1, 2, 3, "hello"}
s2 = Set.new{2, 3, 4, "world"}

s3 = s1 + s2

Set.print(s3)   --{ 1 2 3 4 hello world}

在new集合時將一個公共的mt表作為了集合的元表,那么通過new創建的集合都會有相同的元表,也就有了相同的行為。

再通過定義__add域,則改變了表的“+”行為,使得“+”變成了集合的並集運算。

可以重定義的元方法如下:

__add --加法
__sub --劍法
__mul --乘法
__div --除法
__unm --
__pow --
__concat --連接
__eq    --等於
__lt    --小於
__le    --小於等於
__tostring --字符串輸出
__index    --訪問表的域
__newindex --更新表的域
__metatable --使元表不能被修改

其中最重要的__index,__newindex是用來實現面向對象的關鍵。下面一個對表的監控例子,可以看出__index,__newindex的作用。

original = {} --原始表

mt = {}

mt.__index = function (t, k) --此表的訪問操作,都會訪問original表
    print("access table element " .. tostring(k) .. " : " .. tostring(original[k]))
    return original[k]
end

mt.__newindex = function (t, k, v) --此表的賦值操作,都會操作original表
    print("update table element " .. tostring(k) .. " : " .. tostring(original[k]) .. " to " .. tostring(v))
    original[k] = v
end

t = {} --監控表 用來監控original

setmetatable(t, mt)
t[1] = "hello"            --update table element 1 : nil to hello
str = t[1] .. "world"    --access table element 1 : hello
print(str)                --helloworld

 當我們訪問一個表的不存在的域時,會觸發訪問__index方法。當表缺少一個賦值域時,會觸發訪問__newindex方法。

這兩個重要的元方法是實現面向對象的關鍵。

 

2.類和對象

我們創建一個對象作為其他對象的原型,當調用不屬於該對象的方法時,會去原型中查找。

setmetatable(a, {__index = b})

則b是原型,a是原型的對象。概念上稱b是類,a是b的實例對象。調用a中不存在的對象時,會去b中查找。

Cal = {}

function Cal:New(o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    return o
end

function Cal:Add(a, b)
    print("Cal Add")
    return a + b
end

a = Cal:New()
print(a:Add(5, 6)) --11

調用Cal:New實際會返回一個以Cal自己為元表的新對象。而a中沒有Add方法,則會在Cal中查找Add方法。

3.繼承

有了類與對象,我們需要在此基礎上實現繼承。

Cal = {}

function Cal:New(o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
    return o
end

function Cal:Add(a, b)
    print("Cal Add")
    return a + b
end

SubCal = Cal:New()

function SubCal:Add(a, b)
    print("SubCal Add")
    return a + b
end

a = SubCal:New()
print(a:Add(5, 6)) --11

一個SubCal子類,從基類Cal中繼承了Add方法。a對象會首先在SubCal中查詢方法,如果有則調用,因此子類可以復寫父類的方法。如果沒有則去到父類中查找方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM