天天看點

Lua面向對象

metatable的介紹:

Lua的table可以模拟面向對象,都得益于metatable的強大之處。在一個table中,如果索引一個元素未能找到,解釋器會去該table下的metatable中的__index元素中去尋找,即table.metatable.__index.xxx中尋找。

但是如果要修改table中的元素或指派操作,但是該元素不存在,那麼會在table中建立該元素,而不會去metatable.__index中尋找。

類建立方法的實作方式:

--function.lua

--clone function
function Clone(object)
    local lookup_table = {}--複制元素查找表,為了防止遞歸無限複制元素
    local function _copy(object)
        if type(object) ~= "table" then --不是table元素就直接傳回
            return object
        elseif lookup_table[object] then --是table但是已經在表裡了,代表該table已經複制過了;即該table内如果有元素指向本身時候,複制該元素時就會查到表中有本身的記錄,則直接把本體的table引用傳回。
            return lookup_table[object] 
        end --如果是table并且沒有複制過的table,則深度複制table中的資料,并把此table記錄在表中,防止再次複制
        local new_table = {}
        lookup_table[object] = new_table --記錄時如果Object是元素或函數,則下标是元素名字,如果是table,則下标是table的位址,是以不同層同名table也不會判定重複複制。
        for key, value in pairs(object) do
            new_table[_copy(key)] = _copy(value)
        end
        return new_table
    end
    return _copy(object)
end

--class create
class = function (classname, super)
    local cls
    -- inherited from Lua Object
    if super then
        cls = Clone(super)
        cls.super = super
        if not cls.Ctor then
            cls.Ctor = function() end
        end
    else
        cls = {Ctor = function() end}
    end

    cls.__cname = classname
    cls.__ctype = 2 -- lua
    cls.__index = cls
    
    function cls.New(...)
        local instance = setmetatable({}, cls)
        instance:Ctor(...)
        return instance
    end
    return cls
end
           

分析:

1.class方法中兩個參數,classname代表的是要建立的類的類名,super代表所建立類的基類。首先class判斷是否有supper,如果沒有基類,會為本類建立空的構造方法。如果有基類,則調用Clone方法來克隆基類,這裡Clone方法會把基類的成員函數映射到派生類裡面去。

2.New方法實作:這裡會把構造出來的類的執行個體instance的元表設定成類table自身,并且由于類table自身的__index參數還是類table自身。instance中沒有任何成員函數,用instance調用成員函數的時候,本是找不到函數的,但是因為instance裡面的元表(metatable)存在即指揮類自身,并且類自身裡面還有__index。是以還會去類自身table中的__index中搜尋,此時__index還是指向自身,是以自身的成員方法就能找到了。

3.類自身隻放成員方法,不能放成員變量,原因如下:

   a.由于metatable的原理,即隻有讀的時候會去metatable中找,寫的時候如果instance中沒有,不會去索引metatable而是在instance中新增一個。

   b.由于繼承時候,Ctor構造方法的直接調用者是instance,是以各級構造方法構造的變量都會放到instance中,而非類自身table裡。

Lua多執行個體(一個類可以擷取多個類的對象)基類實作:

--object.lua
local Object = class("Object")
return Object
           

使用方法:

--multi.lua
local Object = require "object"
local Multi = class("Multi", Object)

function Multi:Ctor(...)
    --super constructor
    Object.Ctor(self)
    --class memebers constructor
    self.m_mem = 0
end
function Multi:Show()
    print(self.m_mem)
end
local instance1 = Multi:New()
instance1:Show()
local instance2 = Multi:New()
instance2:Show()
           

第一步:用class方法建立multi類繼承于Object類。

第二步:派生類multi中的構造方法是Object.Ctor的引用,是以要重新寫派生類multi的構造方法。multi類Ctor構造multi的資料時候,首先要在multi中構造父類的資料,是以要在派生類的Ctor中先調用父類的Ctor。這裡必須使用Object.Ctor(self),不能使用Object:Ctor。原理是這樣的:調用起源是New方法中instance:Ctor,Object.Ctor(self)是在instance:Ctor中調用的,是以這裡的self則是instance。如果寫成Object:Ctor,則以後的Ctor參數則變成Object,這樣父級的構造方法構造的資料就放不到instance中了。總體來說由于調用Ctor的調用者是instance,是以之後派生鍊上的所有Ctor方法的參數都是instance,即派生類的執行個體。

第三步:在super.Ctor調用之後構造派生類的成員變量,即self.m_mem = 0這一行。

第四步:定義multi類的方法Show,注意所有成員方法中的self也是instance,因為外部調用成員方法是用instance:func()的。

總結:類中的所有成員方法,包括Ctor方法和普通方法,調用者都是instance,即類的對象。self自然也是instance而非multi自身。

Lua單執行個體類(即一個類隻能初始化一個對象)的實作:

--singleton.lua
local Object  = require "object"
local Singleton = class("Singleton",Object)

function Singleton:GetSingleton(...)
    if self._instance == nil then
	self._instance = self.New(...)
    end 
    return self._instance	
end 

return Singleton    	
           

分析:此處Singleton為單執行個體的最高基類,該類中添加的GetSingleton方法的實質作用是在所有以Singleton為基類的方法中都用該方法擷取執行個體(第一次擷取則初始化執行個體),而不用New方法。此方法的調用者是Singleton或派生類本身,是以傳入的self也是Singleton或派生類本體,這時要在類本體中添加一個_instance的變量,并調用New來擷取一個執行個體放在此變量中,這便是唯一的執行個體。

使用方法:

--single.lua
local Singleton = require "singleton"
local Single = class("Single", Singleton)

function Single:Ctor(...)
    --super constructor
    Singleton.Ctor(self)
    --class memebers constructor
    self.m_mem = 0
end
function Single:Show()
    print(self.m_mem)
end
local instance = Single:GetSingleton()
instance:Show()
           

派生類的重寫構造方法是一樣的,初始化執行個體的時候要用GetSingleton方法而非New方法。

圖像解析兩種基類的記憶體表示:

Lua面向對象
Lua面向對象

繼續閱讀