通过模块实现类

lua-users home
wiki

此系统允许您(滥用)require/模块系统来获得继承的类,而无需调用多余的函数或特殊的类创建器。您需要将以下代码放在一个单独的文件中,该文件将由 require() 一次调用。在包含该文件后,require() 函数将不再执行原始的“包含此文件”功能,而是被以下系统覆盖(或者,您可以更改它,使其不重置 package.loaders 表,而是简单地添加新函数)。

请参阅本页底部有关如何使用的示例。如果您愿意,可以随意将功劳归于 James Rhodes ([email protected]),但没有必要这样做。

代码

-- Overwrite our loading functionality.
package.loaders = {}
package.loaders[1] = function(module, env)
	-- Declare our variables local so they don't
	-- interfere with the main program.
	if (env == nil) then
		env = _G
	end
	
	local path, ext, found_path, e, fhandle
	local chunk, lmodule, _T, lfmodulepath
	local indent, l, n, ldotpos, classname
	local namespace, class_funcs, class, mt
	local __init, f, v, k, tdotpos, __base, i
	local class_inheritance = {}
	local inherit_classname, inherit_namespaces
	local inheritance_sandbox, i2
	local function func_inherit(class)
		if (lmodule._T == nil) then
			lmodule._T = {}
		end
		
		table.insert(class_inheritance, class)
	end
	local function func_name(name)
		local ldotpos, tdotpos, classname, namespaces
		local z, x, c
		ldotpos = string.len(name)
		tdotpos = string.len(name)
		classname = nil
		namespaces = {}
		while (ldotpos > 0) do
			if (string.sub(name,ldotpos,ldotpos) == ".") then
				if (classname == nil) then
					classname = string.sub(name, ldotpos + 1, tdotpos)
				else
					table.insert(namespaces,1,string.sub(name, ldotpos + 1, tdotpos))
				end
				tdotpos = ldotpos - 1
			end
			ldotpos = ldotpos - 1
		end
		if (tdotpos > 0) then
			table.insert(namespaces,1,string.sub(name, ldotpos + 1, tdotpos))
		end
		z = {}
		for c, x in ipairs(namespaces) do
			table.insert(z,1,x)
		end
		namespaces = z
		
		if (lmodule._T == nil) then
			lmodule._T = {}
		end
		
		lmodule._T["NAME"] = classname
		if (#namespaces > 0) then
			lmodule._T["NAMESPACE"] = namespaces
		else
			lmodule._T["NAMESPACE"] = nil
		end
	end
	local function func_description(desc)
		if (lmodule._T == nil) then
			lmodule._T = {}
		end
		
		lmodule._T["DESCRIPTION"] = desc
	end
	local function func_author(author)
		if (lmodule._T == nil) then
			lmodule._T = {}
		end
		
		lmodule._T["AUTHOR"] = author
	end
	local function func_lastmodified(date)
		if (lmodule._T == nil) then
			lmodule._T = {}
		end
		
		lmodule._T["LASTMODIFIED"] = date
	end
	
	-- Replace the "." in the requested module with
	-- backslashes.
	path = string.gsub(module, "%.", "/")
	path = "./" .. path
	ext = {"rcs", "rs", "rks", "lua"}
	found_path = nil
	
	for k, e in pairs(ext) do
		fhandle = io.open(path .. "." .. e, "r")
		if (fhandle ~= nil) then
			fhandle:close()
			found_path = path .. "." .. e
			break
		end
	end
	
	if (found_path == nil) then
		print("ERR : Unable to locate module at " .. path .. ".{rcs,rs,rks,lua}.")
		return nil, "Unable to locate module at " .. path .. ".{rcs,rs,rks,lua}."
	end
	
	-- Since the file exists, we're now going to load it.
	chunk = loadfile(found_path)
	if (chunk == nil) then
		print("ERR : Module " .. module .. " contains syntax errors and cannot be included in the program.")
		return nil, "Module " .. module .. " contains syntax errors and cannot be included in the program."
	end
	
	-- Isolate the class name from the namespace component
	ldotpos = string.len(module)
	while (ldotpos > 0) do
		if (string.sub(module,ldotpos,ldotpos) == ".") then
			break
		end
		ldotpos = ldotpos - 1
	end
	classname = string.sub(module, ldotpos + 1)
	if (ldotpos ~= 0) then
		namespace = string.sub(module, 1, ldotpos - 1)
	else
		namespace = ""
	end
	
	-- Run the chunk() function inside a sandbox, so we can inspect the module.
	lmodule = {}
	lmodule[classname] = {}
	lmodule["inherits"] = func_inherit
	lmodule["name"] = func_name
	lmodule["description"] = func_description
	lmodule["author"] = func_author
	lmodule["lastmodified"] = func_lastmodified
	setfenv(chunk, lmodule)
	chunk()

	-- Check to see if _T exists, if it doesn't, show that the module can't be loaded.
	if (lmodule["_T"] == nil) then
		print("ERR : Module " .. module .. " does not specify module information.")
		return nil, "Module " .. module .. " does not specify module information."
	end
	
	-- Move the _T table from the code block, into a local variable.
	_T = lmodule["_T"]
	lmodule["_T"] = nil
	
	-- Sanitize the _T variable we will use.
	_T["NAME"]			= tostring(_T["NAME"])
	if (_T["DESCRIPTION"] ~= nil) then
		_T["DESCRIPTION"]	= tostring(_T["DESCRIPTION"])
	end
	if (_T["AUTHOR"] ~= nil) then
		_T["AUTHOR"]		= tostring(_T["AUTHOR"])
	end
	if (_T["LASTMODIFIED"] ~= nil) then
		_T["LASTMODIFIED"]	= tostring(_T["LASTMODIFIED"])
	end
	
	-- Verify that the module is located at the correct location (NAMESPACE "." NAME == module)
	if (_T["NAMESPACE"] ~= nil) then
		lfmodulepath = table.concat(_T["NAMESPACE"], ".") .. "." .. _T["NAME"]
		if (lfmodulepath ~= module) then
			print("ERR : Module name mismatch.  Loaded from " .. module .. ", but code specifies " .. lfmodulepath .. ".")
			return nil, "Module name mismatch.  Loaded from " .. module .. ", but code specifies " .. lfmodulepath .. "."
		end
	else
		lfmodulepath = _T["NAME"]
	end
	
	-- Now create the namespace tables if required.
	l = env
	if (_T["NAMESPACE"] ~= nil) then
		for k, n in pairs(_T["NAMESPACE"]) do
			l[n] = {}
			l = l[n]
		end
	end
	
	-- Get the class functions.
	class_funcs = lmodule[classname]
	lmodule[classname] = nil
	__init = nil
	__base = nil
	
	-- Build up the class.
	class = {}
	if (#class_inheritance > 0) then
		-- We need to evaluate all of the inherited class
		-- in order, to build up.
		for k, v in pairs(class_inheritance) do
			-- Isolate the class name from the namespace component
			print("INFO: Loading " .. v .. " in required module.  Namespaces / classes should not leak.")
			ldotpos = string.len(v)
			tdotpos = string.len(v)
			inherit_classname = nil
			inherit_namespaces = {}
			while (ldotpos > 0) do
				if (string.sub(v,ldotpos,ldotpos) == ".") then
					if (inherit_classname == nil) then
						inherit_classname = string.sub(v, ldotpos + 1, tdotpos)
					else
						table.insert(inherit_namespaces,1,string.sub(v, ldotpos + 1, tdotpos))
					end
					tdotpos = ldotpos - 1
				end
				ldotpos = ldotpos - 1
			end
			if (tdotpos > 0) then
				table.insert(inherit_namespaces,1,string.sub(v, ldotpos + 1, tdotpos))
			end
			
			inheritance_sandbox = {}
			package.loaders[1](v, inheritance_sandbox)
			
			-- Load the class into i.
			i = inheritance_sandbox
			for i2, n in pairs(inherit_namespaces) do
				i = i[n]
			end
			i = i[inherit_classname]
			
			-- Now copy all of the inherited classes functions.
			for n, f in pairs(i) do
				if (n == "__init") then
					__base = f
					setfenv(__base, env)
				elseif (type(f) == "function") then
					class[n] = f
					setfenv(class[n], env)
				elseif (type(f) ~= "function") then
					class[n] = f
				end
			end
		end
	end
	for n, f in pairs(class_funcs) do
		if (n == "__init") then
			__init = f
			setfenv(__init, env)
		else
			class[n] = f
			setfenv(class[n], env)
		end
	end
	for n, v in pairs(lmodule) do
		if (n ~= "inherits"
			and n ~= "name"
			and n ~= "description"
			and n ~= "author"
			and n ~= "lastmodified") then
			if (type(v) == "function") then
				print("WARN: " .. lfmodulepath .. ":0: Function " .. n .. " defined without class context.  It is not included in the class definition.")
			else
				-- Make the specified variable a static variable.
				class[n] = v
			end
		end
	end
	mt = {}
	mt.__call = function(class_tbl, ...)
		local obj = {}
		setmetatable(obj, class)
		if (__init ~= nil) then
			__init(obj, ...)
		elseif (__base ~= nil) then
			__base(obj, ...)
		end
		return obj
	end
	class.__index = class
	class.__init = __init
	class.__base = __base
	setmetatable(class, mt)
	
	-- Now put the newly generated class in the namespace.
	l[classname] = class
	
	-- Show module loaded message
	print("INFO: " .. lfmodulepath .. ":0: Loaded module " .. _T["NAME"] .. ".")
	indent = string.rep(" ", string.len("INFO: " .. lfmodulepath .. ":0: "))
	if (_T["NAMESPACE"] ~= nil) then
		print(indent .. "    in namespace " .. namespace)
	end
	if (_T["DESCRIPTION"] ~= nil) then
		print(indent .. "    Description: " .. _T["DESCRIPTION"])
	end
	if (_T["AUTHOR"] ~= nil) then
		print(indent .. "    Author: " .. _T["AUTHOR"])
	end
	if (_T["LASTMODIFIED"] ~= nil) then
		print(indent .. "    Last Modified: " .. _T["LASTMODIFIED"])
	end
	
	return function()
	end
end

用法

每个类都有自己的文件,因此对于下面显示的文件,您将把它放在名为 AnotherNamespace? 的目录中,并将文件命名为 ClassB.lua。请注意,它继承自 MainNamespace?.ClassA,因此您还需要创建该文件(如果您只想测试代码,可以注释掉继承部分)。

重要的是要注意,要继承多个类,您只需多次调用 inherits()。最后一次调用将产生最大的影响,例如,如果两个由 inherits() 引用的类定义了 A() 函数,那么最后一次调用将是 A() 的定义,除非下面的 ClassB 自己覆盖它。即使您可以继承多个类,self.__base() 始终指向最后一个继承类的构造函数。

name "AnotherNamespace.ClassB"
inherits "MainNamespace.ClassA"
description [[Provides advanced class functionality.]]
author "James Rhodes"
lastmodified "20th May, 2010"

myStaticVariable = 5

function ClassB:__init()
	self.__base()
	print "ClassB constructor!"
end

function ClassB:Function()
	print "Called from MyClassFunction (ClassB)"
end


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2010 年 5 月 20 日下午 4:24 GMT (差异)