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的,慢慢積累吧。

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