Lua 中使用面向对象(续)
上一篇文章给了一个面向对象的方案,美中不足的是没有析构函数 Destructor,那么这一次就给它加上。
既然是析构,那么就是在对象被销毁之前做该做的事情,lua 5.1 的 userdata 可以给其 metatable 增加一个 __gc 域,指定一个函数,将会在被回收时调用,这个 __gc 只能用于 userdata,普遍的 table 不支持;到了 lua 5.2 以后,官方支持了给予普通 table 的 metatable 增加 __gc 域,可以在回收前被回调;具体细节可以参考对应版本的 manual。
userdata 一般是 c 里创建的自定义数据结构,但是如果想在 lua 里做这件事情的该如何实现,理论上 lua 是不支持的,但是作者增加了一个隐藏的非公开测试函数 newproxy 用于创建一个空的 userdata,参数可以选择是否带 metatable。用法如下:
newproxy newproxy (boolean or proxy) Undocumented feature of Lua. Arguments: boolean - returned proxy has metatable or userdata - different proxy created with newproxy Creates a blank userdata with an empty metatable, or with the metatable of another proxy. Note that, in ROBLOX, creating a proxy with another proxy is disabled and will error. local a = newproxy(true) local mt = getmetatable(a) print( mt ~= nil ) local b = newproxy(a) print( mt == getmetatable(b) ) local c = newproxy(false) print( getmetatable(c) ~= nil ) print( a.Name ) mt.__index = {Name="Proxy"} print( a.Name ) print( b.Name )
-- Output: true true false attempt to index local 'a' (a userdata value) Proxy Proxy
使用它就可以创建一个空的 userdata 并指定 __gc 操作,在你的对象上保持一个唯一的引用到该 userdata,当你的对象被销毁前 userdata 的 __gc 会被调用。
对于 5.2 及以后版本的 table 的 __gc,需要注意,你必须在第一次为其设置 metatable 时就制定 __gc,才能开启改标记,否则先设置 metatable,而到其后修改 metatable,增加 __gc 域,是不起作用的。
下面给出改进过的面向对象方案,注意这里做了版本区分,TxClass:
-- Get current version number. local _, _, majorv, minorv, rev = string.find(_VERSION, "(%d).(%d)[.]?([%d]?)") local VersionNumber = tonumber(majorv) * 100 + tonumber(minorv) * 10 + (((string.len(rev) == 0) and 0) or tonumber(rev)) -- Declare current version number. TX_VERSION = VersionNumber TX_VERSION_510 = 510 TX_VERSION_520 = 520 TX_VERSION_530 = 530 -- The hold all class type. local __TxClassTypeList = {} -- The inherit class function. local function TxClass(TypeName, SuperType) -- Create new class type. local ClassType = {} -- Set class type property. ClassType.TypeName = TypeName ClassType.Constructor = false ClassType.SuperType = SuperType -- The new alloc function of this class. ClassType.new = function (...) -- Create a new object first and set metatable. local Obj = {} -- Give a tostring method. Obj.ToString = function (self) local str = tostring(self) local _, _, addr = string.find(str, "table%s*:%s*(0?[xX]?%x+)") return ClassType.TypeName .. ":" .. addr end -- Do constructor recursively. local CreateObj = function (Class, Object, ...) local Create Create = function (c, ...) if c.SuperType then Create(c.SuperType, ...) end if c.Constructor then c.Constructor(Object, ...) end end Create(Class, ...) end -- Do destructor recursively. local ReleaseObj = function (Class, Object) local Release Release = function (c) if c.Destructor then c.Destructor(Object) end if c.SuperType then Release(c.SuperType) end end Release(Class) end -- Do the destructor by lua version. if TX_VERSION < TX_VERSION_520 then -- Create a empty userdata with empty metatable. -- And mark gc method for destructor. local Proxy = newproxy(true) getmetatable(Proxy).__gc = function (o) ReleaseObj(ClassType, Obj) end -- Hold the one and only reference to the proxy userdata. Obj.__gc = Proxy -- Set metatable. setmetatable(Obj, {__index = __TxClassTypeList[ClassType]}) else -- Directly set __gc field of the metatable for destructor of this object. setmetatable(Obj, { __index = __TxClassTypeList[ClassType], __gc = function (o) ReleaseObj(ClassType, o) end }) end -- Do constructor for this object. CreateObj(ClassType, Obj, ...) return Obj end -- Give a ToString method. ClassType.ToString = function (self) return self.TypeName end -- The super class type of this class. if SuperType then ClassType.super = setmetatable({}, { __index = function (t, k) local Func = __TxClassTypeList[SuperType][k] if "function" == type(Func) then t[k] = Func return Func else error("Accessing super class field are not allowed!") end end }) end -- Virtual table local Vtbl = {} __TxClassTypeList[ClassType] = Vtbl -- Set index and new index of ClassType, and provide a default create method. setmetatable(ClassType, { __index = function (t, k) return Vtbl[k] end, __newindex = function (t, k, v) Vtbl[k] = v end, __call = function (self, ...) return ClassType.new(...) end }) -- To copy super class things that this class not have. if SuperType then setmetatable(Vtbl, { __index = function (t, k) local Ret = __TxClassTypeList[SuperType][k] Vtbl[k] = Ret return Ret end }) end return ClassType end
使用也很简单:
local MyBase = TxClass("MyBase") function MyBase:Constructor() print("MyBase:Constructor") end function MyBase:Destructor() print("MyBase:Destructor") end local MyNew = TxClass("MyNew", MyBase) function MyNew:Constructor() print("MyNew:Constructor") end function MyNew:Destructor() print("MyNew:Destructor") end local cls = MyNew() cls = nil collectgarbage() -- Output: MyBase:Constructor MyNew:Constructor MyNew:Destructor MyBase:Destructor
接下来的扩展是,给一个简单的运行时方法:IsA。
【Written by yaukey. 若无特殊说明本博客文章均为原创,转载请注明作者yaukey和本博客地址(http://www.cnblogs.com/yaukey/)。】