class 是一个表,不过在它的元表上设置了 __index, __call 方法,所以可以用函数的形式调用。
local class class = setmetatable({},{ __call = function(fun,...) return _class(...) -- 第一种方式建立类 end, __index = function(tbl,key)-- 第二种方式 if key == 'class'then io.stderr:write('require("pl.class").class is deprecated. Use require("pl.class")\n') return class end compat = compat orrequire'pl.compat' local env = compat.getfenv(2) returnfunction(...) local c = _class(...) c._name = key rawset(env,key,c) -- 当前的环境内设置一个 key = c return c end end })
类的建立
上面,我们看到,实际上是 __class 来给我们构造类。我们来逐行查看
---@param base? table|nil 可选的基类 ---@param c_arg 给类构造器的可选参数 ---@param c table 如果传递,那么就将此表变成一个类 localfunction_class(base,c_arg,c) -- 类 `c` 将会是所有它的对象的元表, -- 这些对象会在元表内查询方法。 local mt = {} -- 一个用来让这个类支持 __call 和 __handler 的元表 -- 也可以传递一个只有方法的表 来构造类 没有元表,所以叫 plain table local plain = type(base) == 'table'andnotgetmetatable(base) if plain then-- 基类是一个 Plain 表 c = base -- 这个表就作为类 base = c._base -- 传递的这个表,可以在 _base 指定基类 else-- 基类不是一个 plain 表,构造新类 c = c or {} end
iftype(base) == 'table'then-- 无论是在 plain 表指定的 _base 基类,还是直接传递的基类 -- 我们的新类是基类的浅拷贝! -- 但是要小心,不要把新类中的方法给覆盖掉了 tupdate(c,base,plain) c._base = base -- 如果存在,那就继承 'not found' handler ifrawget(c,'_handler') then mt.__index = c._handler end elseif base ~= nilthen-- 基类只能是一个表或者是 nil error("must derive from a table type",3) end
c.__index = c setmetatable(c,mt) ifnot plain then-- 如果不是 plain 表作为基类 if base andrawget(base,'_init') then c._parent_with_init = base end-- 基类存在,且基类有 _init 方法,那么就继承下来 ,For superFor super and inherited init c._init = nil end
if base andrawget(base,'_class_init') then--基类有初始化方法,则调用 base._class_init(c,c_arg) end
-- 导出一个 ctor 方法,可以通过 <classname>(<args>) 形式调用。 mt.__call = function(class_tbl,...) local obj ifrawget(c,'_create') then obj = c._create(...) end-- 有 _create 方法就调用来构造对象 ifnot obj then obj = {} end-- 默认构造一个空表 setmetatable(obj,c)
ifrawget(c,'_init') orrawget(c,'_parent_with_init') then-- 存在初始化函数 local res = call_ctor(c,obj,...) -- 调用初始化函数 if res then-- 如果 ctor 函数返回了值,这个值就是作为返回对象 obj = res setmetatable(obj,c) end end
if base andrawget(base,'_post_init') then-- 初始化后调用 base._post_init(obj) end
return obj end -- Call Class.catch to set a handler for methods/properties not found in the class! c.catch = function(self, handler) iftype(self) == "function"then -- called using . instead of : handler = self end c._handler = handler mt.__index = handler end c.is_a = is_a c.class_of = class_of c.cast = cast c._class = c
ifnotrawget(c,'__tostring') then c.__tostring = _class_tostring end
return c end
对象的构造过程
对于一个类,
_create 先调用,构造对象
_init 方法 或父类有 _init 方法,那么进行调用,若这步返回的了值,则会替换第一步返回的对象。这一步,会先设置好 super 方法,然后才执行 _init,执行完毕,即删掉 super 方法。
调用基类的 _post_init
第二步是最为复杂的:
localfunctioncall_ctor(c,obj,...) local init = rawget(c,'_init') local parent_with_init = rawget(c,'_parent_with_init')
if parent_with_init then-- 父类有初始化函数 ifnot init then-- 子类没有 init = rawget(parent_with_init, '_init') -- 继承父类 parent_with_init = rawget(parent_with_init, '_parent_with_init') -- 父类的父类还有没有初始化函数 end if parent_with_init then-- super() 函数指向 _init 的所属类的上一级。 rawset(obj,'super',function(obj,...) call_ctor(parent_with_init,obj,...) end) end else -- 没有这句,调用不存在的 super() 有的时候会死循环和栈溢出 rawset(obj,'super',nil) end
local res = init(obj,...) if parent_with_init then-- 调用完毕,就把 super 干掉 rawset(obj,'super',nil) end
return res end
推荐调用父类的形式
在初始化之后,我们如果想要调用父类的相同方法,推荐的形式是这样的:
local A = class.A() functionA:test()end local B = class.B(A) fuction B:test() A.test(B) end