lua面向對象生成實例的正確方式

寫lua這麼久了,也知道怎麼樣用lua來實現所謂的面向對象。下面這段代碼是我常用來new一個新實例對象的:

local Object = {a = 123}
function Object:new (data)  
    local data = data or {}
    setmetatable(data, {__index = self})   
    return data   
end 
local o = Object:new()

以上代碼有沒有問題呢,咋一看是沒有什麼問題,利用元表實現了對象的繼承,凡是新實例都會擁有Object的方法。但是前幾天在實現一段代碼時發現了一個嚴重的問題,新的實例共享了Object的數據,這種情況肯定是不允許出現的,因爲實例壓根不是獨立的實例了。之前也一直沒有注意到這個問題,因爲這個問題比較隱蔽。比方說上面的代碼,改變o.a = 456,是不會影響Object.a的,但是如果Object = {a = {b=123}},生成新實例o後,執行o.a.b = 456,Object對象的數據也跟着變了,後面新生成的實例數據都變了。

這是爲什麼呢?因爲前面在執行o.a = 456時,他是賦值操作,他會給自己的字段a賦值爲456,這裏並不是索引到Object字段。而o.a.b = 456有個獲取字段a的過程,o本身沒有,就從Object中去查找,改變的是Object中a.b的值。所以後面生成實例的值都將是改變之後的值,正確的做法是:

function table.deepcopy(t, nometa)
    local lookup_table = {}
    local function _copy(t,nometa)
        if type(t) ~= "table" then
            return t
        elseif lookup_table[t] then
            return lookup_table[t]
        end
        local new_table = {}
        lookup_table[t] = new_table
        for index, value in pairs(t) do
            new_table[_copy(index)] = _copy(value)
        end
        if not nometa then
           new_table = setmetatable(new_table, getmetatable(t))
        end
        return new_table
    end
    return _copy(t)
end

function Object:new2 (data)  
    data = data or {}
    local copy = table.deepcopy(self)
    setmetatable(data, {__index = copy})   
    return data   
end 

在生成元表的索引時指向的是一個深度拷貝的table,這樣就不會指向原來的值了,該共享函數的還是會共享函數。所以這纔是面向對象生成新實例正確的寫法。

雖然達到了目的,我還是不建議大家這麼寫,首先效率是一個要考慮的因素,在我這篇文章lua代碼優化中有一個對比試驗,可以看出這種寫法效率會大打折扣。其次過多的引入面向對象的東西,在lua這種解釋型語言甚至所有腳本語言中都是不太可取的,增加了代碼的複雜性和不可維護性。

所以建議用lua的table來封裝函數就好,不要模擬生成新實例的方式來表示新的數據結構。你可能會問,我真的需要表示多個相同的實例怎麼辦。這樣的需求時刻存在,但是你可以改變你的思路,用函數的方式來達到目的。這樣你需要改變函數的設計方式,儘可能的用參數,返回值來影響函數的輸出,而不是使用全局變量或所謂的成員變量。這樣相當於把函數當成一個黑盒,相同的輸入總會得到相同的輸出。這也是函數式編程語言中所提倡的,函數是第一等,first-class。比如在過去設計棋牌遊戲中,一張桌子裏面有四把椅子,我會考慮會面向對象的方式來生成四個實例,每個實例中有各種屬性等字段。現在我只會用到一個數據結構table,用它來封裝各種操作函數,然後用傳入的參數來區分不同的椅子即可。

 

 

歡迎加入QQ羣 858791125 討論skynet,遊戲後臺開發,lua腳本語言等問題。

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