Lua:使用元表實現的一種面向對象方法調用
一、Lua中的面向對象編程
Lua中,面向對象編程主要是通過table來實現的。
Lua中,定義對象及方法:
- 冒號定義,冒號引用
local obj = {}
function obj:setname(name)
self.name = name
end
function obj:getname()
return self.name
end
obj:setname("test1280")
print(obj:getname())
或者:
- 點號定義,點號引用
local obj = {}
function obj.setname(self, name)
self.name = name
end
function obj.getname(self)
return self.name
end
obj.setname(obj, "test1280")
print(obj.getname(obj))
或者:
- 冒號定義,點號引用
local obj = {}
function obj:setname(name)
self.name = name
end
function obj:getname()
return self.name
end
obj.setname(obj, "test1280")
print(obj.getname(obj))
或者:
- 點號定義,冒號引用
local obj = {}
function obj.setname(self, name)
self.name = name
end
function obj.getname(self)
return self.name
end
obj:setname("test1280")
print(obj:getname())
使用(冒號或點號)(定義或引用),區別在於是否將對象(table)作爲第一個參數傳入(self、this)。
可見,冒號定義方法或引用方法,是Lua爲我們實現的一種語法糖。使得我們不必顯式地傳入對象本身。
如果既想要通過點號引用方法,又不希望顯式地將對象本身作爲第一個參數傳入方法,如何實現?
一種可行的方法是,通過元表實現。
二、Lua中的元表
Lua中的每個值都有一套預定義的操作集合,例如數字相加減,字符串比較,字符串連接等等。
可以通過元表來修改一個值的行爲,使其在面對一個非預定義的操作時執行一個指定的操作。
Lua中的每個值都有一個元表。
table和userdata可以有各自獨立的元表,其他類型的值則共享其類型所屬的單一元表。
Lua在創建新table時不會創建元表。
可以通過setmetatable來設置一個值的元表;
可以通過getmetatable來獲取一個值的元表;(元表是一個table)
在Lua中只能設置table的元表,在C中可以設置任何值的元表。
當訪問一個table中不存在的字段時,通常會返回nil值;特別的,當這個table的元表有__index元方法時,最終返回結果是__index的返回值。
例如:
local mt = {}
mt.__index = function (t, k)
return rawget(t, "_" .. k)
end
local obj = {}
setmetatable(obj, mt)
obj["_name"] = "test1280"
print(obj["name"])
print(obj["xxxx"])
結果:
[test1280@node1 20190808]$ lua me.lua
test1280
nil
三、設置元表__index元方法,滿足【點號引用對象方法】的需求
-- fn function name
-- fv function value
local mt = {}
mt.__index = function (t, k)
-- obj.fn->obj._fn
local fv = rawget(t, "_" .. k)
if type(fv) ~= "function" then
return
end
-- fv upvalue
-- ... 可變形參
-- 注意返回的是一個函數
return function (...)
return fv(t, ...) -- 強制將t注入到第一個參數
end
end
local obj = {}
setmetatable(obj, mt)
function obj._setname(this, name)
this.name = name
end
function obj._getname(this)
return this.name
end
obj.setname("test1280")
print(obj.getname())
1.設置obj對象的元表,包含__index元方法;
2.obj.setname的過程:
obj本身沒有setname的字段,因此觸發__index元方法;
觸發元方法時,obj對象以及"setname"方法名(字符串)傳入__index元方法(參數);
在__index中嘗試查詢obj是否存在"_setname"函數;
如果存在名字叫做"_setname"的方法(函數),則創建一個新的匿名函數並將其返回;
obj.setname(或者obj["setname"])獲取到新創建的函數;
這個新創建的匿名函數的形參是...,可以接受任何變參,因爲我們無法提前知悉類的每個方法的參數形式(且參數列表也不可能統一類型、數量...);
然後在新匿名函數中通過upvalue的方式引用真實的方法(函數,_setname),強制注入obj對象爲第一個參數;
同時將形參...作爲參數原封不動地傳入upvalue真實的函數;
好吧,有點繞…
以上是我結合元表(__index元方法)和upvalue等原生機制實現的一種通過點號調用對象方法的方法。
雖然調用形式統一,但同時也需要付出性能降低、開銷增大的代價。
有得必有失,每次創建匿名函數可能會影響到性能噢,使用時需要注意。
參考:
1.《Lua程序設計 第二版》