Lua的面向对象机制

先上下参考文章:Lua面向对象

    这个文章写得已经比较清晰了,只是实际用起来的话子类肯定不用像这个文章一样再重写一遍new方法,之前自己用的时候都是拿来直接用的,当时只有一个模糊的印象,这次重新搭环境的时候顺便又看了下,感觉可以单独写个文章记录一下,先上一下代码,基类ClassBase实现:

--lua面向对象模拟
ClassBase = {}

function ClassBase:New(o) 
    local c = o or {}   
    setmetatable(c, self)
    --当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable
    --(假定有metatable)中的__index 键。如果__index包含一个表格,Lua会在表格中查找相应的键。
    self.__index = self
    return c    --返回自身
end

function ClassBase:test_value(val)
    self.testv = val
end

function ClassBase:test_log()
    Log.e("call base func")
end

子类EntityBase继承:

require "GameCore/ClassBase"
--lua面向对象模拟
EntityBase = ClassBase:New()

function EntityBase:OnInit(testValue)
    self.testValue = testValue
end

然后是调用测试:

--主入口函数。从这里开始lua逻辑
function Main()	
	local child1 = EntityBase:New()
	local child2 = EntityBase:New()
	child1:OnInit(1)
	child2:OnInit(2)
	child1:test_value(3)
	child2:test_value(4)
	Log.e("child1 value:"..child1.testValue.." child2 value:"..child2.testValue)
        child1:test_log()
	Log.e("child1 base value:"..child1.testv.." child2 base value:"..child2.testv)
end

unity输出:

这一串代码就是模拟了继承,child1和child2可以达到分别继承基类以及调用和重写基类方法的需求,但是仍然存在一个问题,就是子类覆盖基类方法之后就无法再调用到基类的该方法了。也没有例如C#那样调用base.xxx()来调用基类的方法。这样就不能做类似C#虚方法那样基类实现一部分功能再由子类override后先base.xxx()然后再补充剩下的功能。我试了下直接用getmetatable方法获取到的父对象再调用但方法仍会被子类方法覆盖。虽然我们之前做的项目用上述方法已经可以满足需求,而且上述这个问题可以子类多复制一遍基类代码也可以规避,但毕竟是个问题,所以就上网搜了一下,果然搜到了一些解决方法。

     上述用getmetatable获取到父对象调用方法仍会调用到子类方法的原因可能是lua机制导致的,因为上面代码在初始化子类这个大table的时候就调用了基类New方法,方法内调用的__index以及setmetatable可能导致基类和子类"绑定",这个绑定可能导致在之后初始化子类方法的时候基类方法跟着被覆盖(这里我没有测试过,在网上没搜到相关资料,所以根据结果推论一下可能的原因,如果表达有误欢迎指正讨论)。因为之前看文档以为元表和__index只是建立一个索引关系,但这块我log确实发现调用getmetatable获取到的基类方法被覆盖。然后在网上搜了一下解决方法:第一个方法请点我

      这个方法我没有用到,因为子类也需要实现一下new方法我觉得有点多余,所以又查了下,结果查出了quick-cocos2dx中class类的实现:第二个方法请点我

     这个就对我很有帮助了,大概看了一下感觉代码中有可参考的部分,因此对自己的代码稍做了一些修改,改好之后如下:

新建一个Class类:

function Class(base,o)
    local instance = o or {}
    setmetatable(instance, base)
    instance.base = base
    base.__index = base
    instance.New = function()
        return instance
    end
    return instance
end

然后是改动后的ClassBase类,其实也就是删掉了New方法:

--lua面向对象模拟
ClassBase = {}

function ClassBase:test_value(val)
    self.testv = val
end

function ClassBase:test_log()
    Log.e("call base func")
end

改动后的子类:

require "GameCore/ClassBase"
require "GameCore/Class"
--lua面向对象模拟
EntityBase = Class(ClassBase);
function EntityBase:OnInit(testValue)
    self.testValue = testValue
end

function EntityBase:test_log()
    Log.e("call child func")
end

测试调用:

--主入口函数。从这里开始lua逻辑
function Main()	
	local child1 = EntityBase.New()
	local child2 = EntityBase.New()
	child1:OnInit(1)
	child2:OnInit(2)
	child1:test_value(3)
	child2:test_value(4)
	Log.e("child1 value:"..child1.testValue.." child2 value:"..child2.testValue)
	child1.base:test_log()--调用基类log
	child1:test_log()--调用子类log
	Log.e("child1 base value:"..child1.testv.." child2 base value:"..child2.testv)
end

最后是Unity输出:

可以看到使用super就可以调用到基类方法。这个写法和第一个方法主要的区别就是Class方法中第一次设置setmetatable和__index之后虽然子类初始化自己之后会产生覆盖,但是在调用New时返回的是一个闭包,也就是被覆盖之前的基类,而又因为基类在第一次返回之前就已经设置好了元表和__index对应关系,所以仍然能访问到子类的方法,而且base内的基类也不会被覆盖。

最后:

    因为写的有点仓促,所以代码没有详细整理,回头我有空的话再看看哪里有问题的话再做修改。写这个帖子的时候本来是两点调出来的运行结果,但是一边写文章一边又一边在改代码,搞完已经四点多了,感觉这会最终版和两点时候的代码基本又变了一个样子。。然后写的可能有点乱,因为今天感冒加这会时间太晚了写文章的时候脑子已经昏了,如果有问题欢迎探讨。

最后还想说下lua这边以后可能会慢慢发一些帖子,因为自从来深圳以来一直没时间写博客了,之前linq应该也是坑掉了,因为年初一来就开始用lua,前阵子项目被砍现在又要用laya和typescript做微信小游戏,心里一下就崩掉了。。内心实打实还是想做u3d,然后前阵子在tap上看到一个叫做僵尸小镇的游戏,作者16年开始自学u3d,用playmaker都能写出来。。而且pvp联网看着也没什么问题,心里也因此有点小受打击。总觉得自己该做些什么,,可能是换了环境之后才发现自己身上的不足吧,总之这段时间希望能坚持写点啥出来,然后期间遇到的问题有时间就总结下写个文章,应该大部分都是lua的,慢慢积累吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章