Lua本身是没有面向对象支持的,但面向对象编程在逻辑复杂的大型工程却很有用。于是很多人用Lua本身的数据结构table来模拟面向对象。最简单的一种方法是把对象的方法、成员都放到table中。如:
-- file:test.lua
local test = {}
function test:get_x()
return self.x or 0
end
function test:set_x( _x )
self.x = _x
end
local test_module = {}
function test_module.new()
local t = {}
for k,v in pairs( test ) do
t[k] = v
end
return t
end
return test_module
调用也比较简单:
-- file:main.lua
local test = require "test"
local _t = test.new()
_t:set_x( 999 )
print( _t:get_x() )
这已经很像面向对象编程。但我们可以看到这样写有些缺点:
1.数据和方法混在一起(当然这不是什么大问题,C++也是这样)
2.每创建一个对象,都要将方法复制一遍
3.没法继承
Lua有强大的元表(metatable),利用它我们可以更优雅地封装一下:
1.先统一封装一个面向对象函数:
-- file:oo.lua
local oo = {}
local cls = {}
local function new( clz )
local t = {}
setmetatable(t, clz)
return t
end
function oo.class( parent,name )
local t = {}
cls[name] = t
parent = parent or {}
rawset( t,"__index",t )
setmetatable( t,{ __index = parent,__call = new } )
return t
end
return oo
2.然后重新写类的实现:
-- file:test.lua
local oo = require "oo"
local test = oo.class( nil,... )
function test:get_x()
return self.x or 0
end
function test:set_x( _x )
self.x = _x
end
return test
3.调用也更加简单了:
-- file:main.lua
local Test = require "test"
local _t = Test()
_t:set_x( 999 )
print( _t:get_x() )
可以看到,利用元表,我们可以把方法全部放到元表中,与对象成员数据分开。元表本身是一个表,它也有元表,可以把父类作为元表的元表实现继承。我们如果再扩展一下,还可以实现对象方法的热更,对象统计...
虽然元表很巧妙,但它的实现是有代价的。Lua得先在table中查找是否有相同的值,如果没有,再去元表找。如果是多重继承,那么还得一层层元表找下去。下面我们来测试一下元表的效率。
-- lua metatable performance test
-- 2016-04-01
-- xzc
local test = function( a,b ) return a+b end
local empty_mt1 = {}
local empty_mt2 = {}
local empty_mt3 = {}
local empty_mt4 = {}
local empty_mt5 = {}
local empty_mt6 = {}
local empty_mt7 = {}
local empty_mt8 = {}
local mt = {}
mt.test = test
local mt_tb = {}
setmetatable( empty_mt8,{__index = mt} )
setmetatable( empty_mt7,{__index = empty_mt8} )
setmetatable( empty_mt6,{__index = empty_mt7} )
setmetatable( empty_mt5,{__index = empty_mt6} )
setmetatable( empty_mt4,{__index = empty_mt5} )
setmetatable( empty_mt3,{__index = empty_mt4} )
setmetatable( empty_mt2,{__index = empty_mt3} )
setmetatable( empty_mt1,{__index = empty_mt2} )
setmetatable( mt_tb,{__index = empty_mt1} )
local tb = {}
tb.test = test
local ts = 10000000
f_tm_start()
local cnt = 0
for i = 1,ts do
cnt = test( cnt,1 )
end
f_tm_stop( "call function native" )
f_tm_start()
local cnt = 0
for i = 1,ts do
cnt = tb.test( cnt,1 )
end
f_tm_stop( "call function as table value" )
f_tm_start()
for i = 1,ts do
cnt = empty_mt6.test( cnt,1 )
end
f_tm_stop( "call function with 3 level metatable" )
f_tm_start()
for i = 1,ts do
cnt = mt_tb.test( cnt,1 )
end
f_tm_stop( "call function with 10 level metatable" )
在我的笔记本上测试,结果为:
local ts = 10000000
call function native 1091772 microsecond
call function as table value 1287172 microsecond
call function with 3 level metatable 2014431 microsecond
call function with 10 level metatable 3707181 microsecond
可以看到,采用第一种方法封闭的面向对象比原生函数调用慢不了多少,但用第二种方法实现3重继承的话,几乎慢了一倍。
在实际项目中,我们用的是第二种封装方式,最主要是可以继承和热更代码。虽然效率有一定影响,但实际应用中逻辑消耗的时间比函数调用的时间仍大得多,这点损耗可以接受。这个世界上没有最快,只有更快,不必盯着程序的效率看。在满足项目要求的情况下,开发效率也是很值得考虑的。
Lua面向对象封装及元表(metatable)性能测试
发表于:2017-01-09
作者:网络转载
来源:
- 周排行
- 月排行
-   软件测试之异常测试
-   MongoDB大数据高并发读写性能测试报告
-   可靠性测试教程:优秀实践综合指南
-   可靠性测试的基础知识——软件可靠性测试
-   压力测试如何准备数据?思路来了!
-   性能测试准入准出规范
-   性能测试常见术语浅析
-   Docker容器网络性能测试和调优策略
-   软件测试之异常测试
-   如何使用k6做性能测试
-   性能测试准入准出规范
-   稳定性测试怎么做,这篇文章彻底讲透了!
-   干货|性能测试模型初探及应用方法分析
-   可靠性测试教程:优秀实践综合指南