熟練使用Lua(四)面向物件:基於table的面向物件實現(1)
轉:https://www.cnblogs.com/yao2yaoblog/p/6433553.html
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中查詢方法,如果有則呼叫,因此子類可以複寫父類的方法。如果沒有則去到父類中查詢方法。