lua实现面向对象编程是基于元表
metatable
,元方法__index
来实现的。
lua中的语法糖
lua的table中可以定义函数,如下图:
local a = {
x = 1,
print = function(obj)
print("x=" .. obj.x)
end
}
如上图,定义了一个名叫 a
的table,可以通过调用 a.print(a)
来打印table a
中的 x
key对应的value值。
像是 a.print(a)
这种形式,在lua中可以简化为 a:print()
,这是lua中的语法糖,使用 :
而不是 .
,lua会默认把 “调用者” 给传递进去。
元表 metatable
元表的表现行为类似于操作符重载,比如我们可以重载 __add,来计算两个 Lua 数组的并集;或者重载 __tostring,来定义转换为字符串的函数。
Lua 提供了两个处理元表的函数:
- setmetatable(table, metatable), 用于为一个 table 设置元表
- getmetatable(table),用于获取 table 的元表
比如:
local version = { major = 1, minor = 1, patch = 1 }
version = setmetatable(version,
{
__tostring = function(t)
return string.format("%d.%d.%d", t.major, t.minor, t.patch)
end
})
print(tostring(version)) -- 1.1.1
这段代码的作用就是重载了 version
表的 tostring()
方法,如果不重载的话,直接调用默认方法,那么打印结果是:
local version = { major = 1, minor = 1, patch = 1 }
print(tostring(version)) -- table: 0x000647a8
元方法: __index
我们在 table 中查找一个元素时,首先会直接从 table 中查询,如果没有找到,就继续到元表的 __index 中查询。
local version = { major = 1, minor = 1}
version = setmetatable(version,
{
__index = function(t, key)
if key == "patch" then
return 2
end
end,
__tostring = function(t)
return string.format("%d.%d.%d", t.major, t.minor, t.patch)
end
})
print(tostring(version)) -- 1.1.2
这样当 t.patch
取不到值时,就会去 __index
中进行查找。
__index 不仅可以是一个函数,也可以是一个table。
local version = { major = 1, minor = 1}
version = setmetatable(version,
{
__index = { patch=3 },
__tostring = function(t)
return string.format("%d.%d.%d", t.major, t.minor, t.patch)
end
}
)
print(tostring(version)) -- 1.1.3
上面的代码可以变换为如下形式:
local version = { major = 1, minor = 1}
local mt = {
__index = {
patch=3
},
__tostring = function(t)
return string.format("%d.%d.%d", t.major, t.minor, t.patch)
end
}
version = setmetatable(version, mt)
print(tostring(version)) -- 1.1.3
元方法:__call
使用 __call
可以让 table 被调用。
local version = { major = 1, minor = 1, patch = 1 }
local function print_version(t)
print(string.format("%d.%d.%d", t.major, t.minor, t.patch))
end
version = setmetatable(version,
{
__call = print_version
}
)
version() -- 1.1.1
面向对象
lua-resty-mysql 是OpenResty 官方的 MySQL 客户端,里面就使用元表模拟了类和类方法,它的使用方式如下所示:
local mysql = require "resty.mysql" -- 先引用 lua-resty 库
local db, err = mysql:new() -- 新建一个类的实例
db:set_timeout(1000) -- 调用类的方法
那么lua-resty-mysql的源码是如何实现的呢?
local _M = { _VERSION = '0.21' } -- 使用 table 模拟类
local mt = { __index = _M } -- mt 即 metatable 的缩写,__index 指向类自身
-- 类的构造函数
function _M.new(self)
local sock, err = tcp()
if not sock then
return nil, err
end
return setmetatable({ sock = sock }, mt) -- 使用 table 和 metatable 模拟类的实例
end
-- 类的成员函数
function _M.set_timeout(self, timeout)
-- 使用 self 参数,获取要操作的类的实例
local sock = self.sock
if not sock then
return nil, "not initialized"
end
return sock:settimeout(timeout)
end
上面的代码中,-M
这个table代表的就是mysql类,_M.new(self)
方法中返回一个 setmetatable({ sock = sock }, mt)
就是返回一个类的对象,socket
这个属性就是这个对象的成员属性,而不是类的属性。