Lua:使用元表實現的一種面向對象方法調用

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程序設計 第二版》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章