Skip to content

lua面向对象

上篇文章简单实现了面向对象功能,这次,我们来说说实现lua继承及添加构造函数

先理解一个概念self

lua
local cc = class()

function cc:test()
	self.d1 = 1
end

function cc.test(self)
	self.d1 = 2
end

return cc

按照上篇文章所说个人习惯,将上述代码保存为test.lua

lua
local test = require 'test'
local d = test.new()
d:test()

print(d.d1) --2

你可能会疑问 test文件中到底调用了哪个test方法呢,self又是从哪里来的呢?
其实test文件中的2个test方法完全是等效的,只不过我们使用 : 时,隐式的传了一个参self (即d:test()传了一个参数d本身),所以也可以写作d.test(d)

所以self其实只是一个传递的参数而已,它并不等于cc本身(在这里,self其实是obj,而cc则是_class)

实现lua的继承

lua
function class(super)
	local _class = {__mode='kv'} --class对象 ?__mode弱表引用 应该要设置元表吧?
	_class.super = super

	_class.new = function(...)
		local obj = {} --new出来的对象
		
		local create
		create = function(c,...)
			if c.super then
				create(c.super,...)
			end
			if c.ctor then
				c.ctor(obj,...)
			end
		end
		create(_class,...)

		setmetatable(obj,{__index=_class}) --指向_class的成员方法
		return obj
	end

	--实现单例模式
	_class.instance = function(...)
		if not _class._instance then
			_class._instance = _class.new(...)
		end
		return _class._instance
	end

	if super then --若父类存在,指向父类的成员方法
		-- setmetatable(_class,{__index=_class.super}) --这里有个bug,当子类无构造函数时,会导致父类构造函数执行多次,暂时修改为下面代码(2018/03/17)
        setmetatable(_class,{__index = function(t,k)
			if k == 'ctor' then
				return t[ctor]
			else
				return _class.super[k]
			end
		end})
	end

	return _class
end

这次直接贴完整代码了,上面代码完整的实现了构造函数、方法继承,顺便还添加了个常用的单例模式

  • new方法中的递归方法create,主要目的是执行所有父类的构造函数ctor,因为ctor传参为obj,所以在ctor中所有定义的self.字段名 其实即为obj的变量
  • super是父类,当父类存在时,需要将_class的元表指向父类,保证我们找到父类的成员
  • 单例就不用说了,几行简单的代码,应该都能看得懂

测试是否实现了继承

先贴下代码 test1.lua (test1继承test2,test2继承test3)

lua
require 'class'
local test2 = require 'test2'
local cc = class(test2)

function cc:ctor(d)
	print('test  ctor   self:'..tostring(self)..'  cc:'..tostring(cc))
end

function cc:test()
	self.p_test = 'test'
	-- self.super.test(self)
	-- self.super:test()
	-- cc.super:test()
	cc.super.test(self)
	print('test  execute   self:'..tostring(self)..'  cc:'..tostring(cc)..'  self.super:'..tostring(self.super))
end

return cc

test2.lua

lua
local test3 = require 'test3'
local cc = class(test3)

function cc:ctor()

	print('test2 ctor   self:'..tostring(self)..'  cc:'..tostring(cc))
end

function cc:test()
	self.p_test2 = 'test2'
	-- self.super.test(self)
	-- self.super:test()
	-- cc.super:test()
	cc.super.test(self)
	print('test2 execute   self:'..tostring(self)..'  cc:'..tostring(cc)..'  self.super:'..tostring(self.super))
	-- print('p_test:'..self.p_test)
	-- print('p_test3:'..self.p_test3)
end

return cc

test3.lua

lua
local cc = class()

function cc:ctor()

	print('test3 ctor   self:'..tostring(self)..'  cc:'..tostring(cc))
end

function cc:test()

        self.p_test3 = 'test3'
	print('test3 execute   self:'..tostring(self)..'  cc:'..tostring(cc))
end

return cc

测试代码

lua
local test = require 'test1'
local d = test.new(11)
d:test()

执行测试代码,运行ok,做下解释(这就需要你能理解我前面所讲解的self概念了) 你可能看到了在调用父类方法时,有四种写法

  1. self.super.test(self)
  2. self.super:test()
  3. cc.super:test()
  4. cc.super.test(self)

其实2,3,4的写法都是ok的,都可以正常调用父类的方法,但关键在于我们需要在各自方法中访问self,这就会造成问题。我们所期待的继承关系是 子类可以访问父类的成员,父类同样可以访问子类的成员。

  • 分析1:因为调用d:test(),所以test1中的self其实是d本身,也就是new出来的对象obj,self.super其实是拿obj.super,没有则从_class中取,可以正常调用test2中的test方法,然而test2中拿到的self.super还是test2本身,造成循环调用,堆栈溢出
  • 分析2:我们把test2中的print解开,会发现提示找不到self.p_test。 因为p_test是在test1.lua中定义的,所以只属于test1,而在test2中拿到的self是test2本身,并不存在p_test字段。
  • 分析3:3其实同2是一样的,都属于可以访问到父类的成员,却无法访问到子类的成员。

结论:因为我们需要在各自的方法体里准确找到其父类,所以 采用 cc.super
因为我们需要在父类中访问子类的成员,所以可以将所有访问的对象都设置为子类,所以将子类当参数传递 即cc.super.test(self)