原文地址:https://zhuanlan.zhihu.com/p/123971515
本篇文章介绍的是 lua 的几种 class 实现。适合有 lua 开发经验和了解 oop 编程的开发人员阅读。
一、Lua 实现 class 的基本方式
众所周知,lua 可以通过元表来实现一些骚操作,目前主流的 class 实现也是通过元表来实现的。
local t = {}
local mt = {
__index = {
a = 10,
b = 20,
add = function(a, b)
return a + b
end
}
}
setmetatable(t, mt)
print(string.format("%d + %d = %d", t.a, t.b, t.add(t.a, t.b)))
-- 10 + 20 = 30
通过元表 mt 的 index 字段可以让表 t 获得一些本身没有的字段, 通过这样的一个形式,我们就可以达到从实例中调用类的方法,但是实例的成员变量又是相互独立的。另外,__index 也可以是方法,这里只简单介绍使用的关键部分,有兴趣的可以看下附链接。
Lua 元表(Metatable)www.runoob.com
通过元表就可以简单的实现一个 class 函数,用来作为类的声明了:
-- 声明一个 lua class
-- className 是类名
-- super 为父类
local function class(className, super)
-- 构建类
local clazz = { __cname = className, super = super }
if super then
-- 设置类的元表,此类中没有的,可以查找父类是否含有
setmetatable(clazz, { __index = super })
end
-- new 方法创建类对象
clazz.new = function(...)
-- 构造一个对象
local instance = {}
-- 设置对象的元表为当前类,这样,对象就可以调用当前类生命的方法了
setmetatable(instance, { __index = clazz })
if clazz.ctor then
clazz.ctor(instance, ...)
end
return instance
end
return clazz
end
以上就是一个简单的 class 实现,通过 metatable 的嵌套设置, 可以保证实例一定可以访问到含有同名函数的最近一个父类的方法。
下面为测试
local printf = function(str, ...)
return print(string.format(str, ...))
end
-- 声明 classA
local ClassA = class("ClassA")
ClassA.static = 'Static A'
function ClassA:ctor(a, b)
self.a = a or 0
self.b = b or 0
end
function ClassA:print()
printf("%s, a = %s, b = %d, static = %s", self.__cname, self.a, self.b, self.static)
end
function ClassA:getSum()
return self.a + self.b
end
-- 声明 classB, 并且继承 ClassA
local ClassB = class("ClassB", ClassA)
function ClassB:ctor(...)
self.super.ctor(self, ...)
end
-- overwrite
function ClassB:print()
print('Class B overwrite super print')
end
-- 声明 classC, 并且继承 ClassB
local ClassC = class("ClassC", ClassA)
ClassC.static = 'Static C'
local obja1 = ClassA.new(10, 20)
local obja2 = ClassA.new(100, 200)
local objb1 = ClassB.new(1, 2)
local objc = ClassC.new(3, 4)
obja1:print()
obja2:print()
objb1:print()
objc:print()
printf("3 + 4 = %s", objc:getSum())
--[[
ClassA, a = 10, b = 20, static = Static A
ClassA, a = 100, b = 200, static = Static A
Class B overwrite super print
ClassC, a = 3, b = 4, static = Static C
3 + 4 = 7
]]
以上就是实现了一个基本的 class 实现,接下来去对比、赏析一些其他的 class 实现。
以下部分完全搬运,会附上链接,最后阶段会添加一段对这些实现的个人看法。
二、cocos2dx-lua 中的 class 实现
function class(classname, super)
local superType = type(super)
local cls
if superType ~= "function" and superType ~= "table" then
superType = nil
super = nil
end
if superType == "function" or (super and super.__ctype == 1) then
-- inherited from native C++ Object
cls = {}
if superType == "table" then
-- copy fields from super
for k,v in pairs(super) do cls[k] = v end
cls.__create = super.__create
cls.super = super
else
cls.__create = super
cls.ctor = function() end
end
cls.__cname = classname
cls.__ctype = 1
function cls.new(...)
local instance = cls.__create(...)
-- copy fields from class to native object
for k,v in pairs(cls) do instance[k] = v end
instance.class = cls
instance:ctor(...)
return instance
end
else
-- inherited from Lua Object
if super then
cls = {}
setmetatable(cls, {__index = super})
cls.super = super
else
cls = {ctor = function() end}
end
cls.__cname = classname
cls.__ctype = 2 -- lua
cls.__index = cls
function cls.new(...)
local instance = setmetatable({}, cls)
instance.class = cls
instance:ctor(...)
return instance
end
end
return cls
end
chukong/quick-cocos2d-xgithub.com[图片上传失败...(image-8e33fe-1610433106438)]
三、toluaframework 中的 class 实现
--Author : Administrator
--Date : 2014/11/25
--声明,这里声明了类名还有属性,并且给出了属性的初始值。
LuaClass = {x = 0, y = 0}
--这句是重定义元表的索引,就是说有了这句,这个才是一个类。
LuaClass.__index = LuaClass
--构造体,构造体的名字是随便起的,习惯性改为New()
function LuaClass:New(x, y)
local self = {}; --初始化self,如果没有这句,那么类所建立的对象改变,其他对象都会改变
setmetatable(self, LuaClass); --将self的元表设定为Class
self.x = x;
self.y = y;
return self; --返回自身
end
--测试打印方法--
function LuaClass:test()
logWarn("x:>" .. self.x .. " y:>" .. self.y);
end
jarjin/LuaFramework_UGUI_V2github.com
图标三、xlua-framework 中的 class 实现
--[[
-- added by wsh @ 2017-11-30
-- Lua面向对象设计
--]]
--保存类类型的虚表
local _class = {}
-- added by wsh @ 2017-12-09
-- 自定义类型
ClassType = {
class = 1,
instance = 2,
}
function BaseClass(classname, super)
assert(type(classname) == "string" and #classname > 0)
-- 生成一个类类型
local class_type = {}
-- 在创建对象的时候自动调用
class_type.__init = false
class_type.__delete = false
class_type.__cname = classname
class_type.__ctype = ClassType.class
class_type.super = super
class_type.New = function(...)
-- 生成一个类对象
local obj = {}
obj._class_type = class_type
obj.__ctype = ClassType.instance
-- 在初始化之前注册基类方法
setmetatable(obj, {
__index = _class[class_type],
})
-- 调用初始化方法
do
local create
create = function(c, ...)
if c.super then
create(c.super, ...)
end
if c.__init then
c.__init(obj, ...)
end
end
create(class_type, ...)
end
-- 注册一个delete方法
obj.Delete = function(self)
local now_super = self._class_type
while now_super ~= nil do
if now_super.__delete then
now_super.__delete(self)
end
now_super = now_super.super
end
end
return obj
end
local vtbl = {}
_class[class_type] = vtbl
setmetatable(class_type, {
__newindex = function(t,k,v)
vtbl[k] = v
end
,
--For call parent method
__index = vtbl,
})
if super then
setmetatable(vtbl, {
__index = function(t,k)
local ret = _class[super][k]
--do not do accept, make hot update work right!
--vtbl[k] = ret
return ret
end
})
end
return class_type
end
四、lua users 中的 class 实现
function class(def)
local class = {}
local parents = {}
local upv
local env = _G
local wraps
local function super(parent_class)
if not parent_class then
parent_class = parents[1]
end
local this = this
local that = {}
for k, v in pairs(parent_class) do
that[k] = type(v) == 'function' and wraps(this, v) or v
end
return setmetatable(that, that)
end
function wraps(this, func)
return function(...)
local t = env.this
local s = env.super
env.this = this
env.super = super
local ret = pcall(func, ...)
env.this = t
env.super = s
return ret
end
end
function class.__init()
end
for i = 1, math.huge do
inherit, v = debug.getlocal(def, i)
if not inherit then
break
end
local parent_class = _G[inherit]
for i = 1, math.huge do
local name, pclass = debug.getlocal(2, i, 1)
if not name then
break
elseif name == inherit then
parent_class = pclass
break
end
end
if parent_class and type(parent_class) == 'table' then
table.insert(parents, parent_class)
for k, v in pairs(parent_class) do
class[k] = v
end
else
error(string.format('Class "%s" not valid.', name))
end
end
for i = 1, math.huge do
local name, value = debug.getupvalue(def, i)
if not name then
break
elseif name == '_ENV' then
env = value
upv = i
break
end
end
local _env = setmetatable({}, {
__index = function(t, name)
local value = class[name]
return value ~= nil and value or env[name]
end,
__newindex = function(t, name, value)
class[name] = value
end
})
local function senv(env)
if upv then
debug.setupvalue(def, upv, env)
else
_G = env
end
end
senv(_env)
env.pcall(def, env.table.unpack(parents))
senv(env)
return setmetatable({}, {
__ipairs = function()
return ipairs(class)
end,
__pairs = function()
return pairs(class)
end,
__index = function(t, name)
return class[name]
end,
__index_new = function(t, name, value)
class[name] = value
end,
__call = function(...)
local this = {}
for k, v in pairs(class) do
this[k] = type(v) == 'function' and wraps(this, v) or v
end
this.__class = class
this.__init(...)
return setmetatable(this, this)
end
})
end
--Example:
global = true
Inherit = class(function()
this_is_a_property_of_Inherit = true
function __init()
print('Inherit().__init()')
this.init = true
end
function __call()
print('Yay! You\'re calling for me :) init:', this.init, '\n')
end
end)
Example = class(function(Inherit)
property = "Class property"
print('Inherited property:', this_is_a_property_of_Inherit)
print('Global variable: ', global, '\n')
function __init()
print('Example().__init()')
super().__init()
print('this.init:', this.init)
end
function test(...)
print(..., this.__init, '\n')
end
end)
example = Example()
example.test('__init:')
--example()
--
----example.property = 'I\'m a property of instance "example"'
--print('example.property', example.property)
--print('Example.property', Example.property)
Simple Lua Classeslua-users.org
这个实现方式很特殊,不是那么直白的使用 metatable 去实现,而是通过 debug API 实现的。熟悉 lua 的朋友可能看得出来这个类似与 setfenv,因为只有 lua 5.1 才有 setfenv 和 getfenv,这个是 lua5.3 上的实现。关于 5.3 上的实现可以参考
https://leafo.net/guides/setfenv-in-lua52-and-above.htmlleafo.net
五、总结
纵观以上的 class 方式实现,可以分为两种实现方法,一种是 metatable, 一种是 function env,对于刚接触 lua 的来说,可能会喜欢第二种,它也和 JavaScript 类似。不过在商业开发中,更多的还是要考虑可读性、 IDE 的配合、 breakpoint 的可行性,还是建议使用 metatable 的方式。
对于我个人而言,比较倾向第一种(毕竟我自己写的)或者 cocos2d-x 的实现方式,比较清晰。tolua 的实现是基本每个类都要自己写,不太方便扩展和协同,xlua 目前新加入项目使用的就是这个,槽点比较多:
- 多加了一个析构函数,其实这个没必要,另外写一个 IDispose 继承即可
- 默认 __init 为构造函数,个人还是喜欢 ctor 或者和 TypeScript 一样写 constructor 一样会更好理解。
- 最后 return 那一块 *--do not do accept, make hot update work right! *显得多余了,做所谓的暖更新一没必要,二可能会出现意向不到的错误,重启 lua 虚拟机是一个比较稳妥的方案。
这里推荐一款 lua IDE 插件 -- EmmyLua,可以被安装下 vscode 或者 jetbrian 系列上,可以对 lua 进行语法分析、断点调试。
网友评论