1. 程式人生 > >Lua面向物件實現

Lua面向物件實現

lua中沒有類的概念,一般所說的類其實就是一個table實現的。關於lua類有兩種實現方式
第一種是在cocos2d-x引擎目錄下圖示檔案

function clone(object)
    local lookup_table = {}
    local function _copy(object)
        if type(object) ~= "table" then
            return object
        elseif lookup_table[object] then--查詢是否已複製過該物件
            return lookup_table[object]
        end
        local new_table = {}
        lookup_table[object] = new_table
        for key, value in pairs(object) do
            new_table[_copy(key)] = _copy(value)--遞迴複製
        end
        --同時複製lua元表
        return setmetatable(new_table, getmetatable(object))
    end
    return _copy(object)
end

--Create an class.
function class(classname, super)
    local superType = type(super)
    local cls
    --如果父類既不是函式也不是table則說明父類為空
    if superType ~= "function" and superType ~= "table" then
        superType = nil
        super = nil
    end

    --如果父類的型別是函式或者是C物件
    if superType == "function" or (super and super.__ctype == 1) then
        -- inherited from native C++ Object
        cls = {}

        --如果父類是表則複製成員並且設定這個類的繼承資訊
        --如果是函式型別則設定構造方法並且設定ctor函式
        if superType == "table" then
            -- copy fields from super
            for k,v in pairs(super) do cls[k] = v end
            cls.__create = super.__create
            cls.super    = super
        else
            cls.__create = super
        end
        --設定型別的名稱
        cls.ctor    = function() end
        cls.__cname = classname
        cls.__ctype = 1

        --定義該型別建立例項的函式為基類的建構函式後複製到子類例項
        --並且呼叫子數的ctor方法
        function cls.new(...)
            local instance = cls.__create(...)--呼叫基類建構函式
            -- copy fields from class to native object
            for k,v in pairs(cls) do instance[k] = v end
            instance.class = cls
            instance:ctor(...)
            return instance
        end

    else
        --如果是繼承自普通的lua表,則設定一下原型,並且構造例項後也會呼叫ctor方法
        -- inherited from Lua Object
        if super then
            cls = clone(super)
            cls.super = super
        else
            cls = {ctor = function() end}
        end

        cls.__cname = classname
        cls.__ctype = 2 -- lua 1表示繼承自C++物件;2表示繼承自lua表物件
        cls.__index = cls

        function cls.new(...)
            local instance = setmetatable({}, cls)
            instance.class = cls
            instance:ctor(...)
            return instance
        end
    end

    return cls
end

通過閱讀上述程式碼可以看出,如果是繼承自c++,則無需我們手動呼叫基類建構函式,如果是繼承Lua表,則需要手動呼叫建構函式,例如:

/定義名為 Shape 的基礎類  
local Shape = class("Shape")  
  
  
//ctor() 是類的建構函式,在呼叫 Shape.new() 建立 Shape 物件例項時會自動執行  
function Shape:ctor(shapeName)  
    self.shapeName = shapeName  
    printf("Shape:ctor(%s)", self.shapeName)  
end  
  
//為 Shape 定義個名為 draw() 的方法  
function Shape:draw()  
    printf("draw %s", self.shapeName)  
end  
  
// Circle 是 Shape 的繼承類  
local Circle = class("Circle", Shape)  
  
  
function Circle:ctor()  
    -- 如果繼承類覆蓋了 ctor() 建構函式,那麼必須手動呼叫父類建構函式  
    -- 類名.super 可以訪問指定類的父類  
    Circle.super.ctor(self, "circle")  
    self.radius = 100  
end  
  
  
function Circle:setRadius(radius)  
    self.radius = radius  
end  
  
  
// 覆蓋父類的同名方法  
function Circle:draw()  
    printf("draw %s, raidus = %0.2f", self.shapeName, self.raidus)  
end  

可以看到Circle類的建構函式裡呼叫了基類的建構函式,但是這樣使用顯得程式碼不美觀,也不方便使用,有沒有更好的使用辦法呢?答案是有的,這裡我結合網上眾多資料以及個人理解做一下總結:

第一種方案:

我們可以將呼叫基類的函式封裝出來,如下:


function super(o,...)
    --if (o and o.super and o.super.ctor) then
        o.super.ctor(o,...)
    --end
end

這樣在呼叫的時候直接傳進來引數就可以了,不過還是要手動呼叫

第二種方案:

結合class類的實現,我們在建立類的時候可以這樣寫

class函式第二個引數我們直接傳入一個函式,按照程式原意中繼承c++的流程執行,最後也能實現

第三種方案:

反覆斟酌class類實現,可以發現,當我們從c++繼承時能夠自動呼叫建構函式,當從lua繼承時能不能也自動實行建構函式呢,經過摸索,發現利用元表確實可以實現,以下是我的做法

function class(classname, super)
    local superType = type(super)
    local cls
    if superType ~= "function" and superType ~= "table" then
        superType = nil
        super = nil
    end

    if superType == "function" or (super and super.__ctype == 1) then
        print("--inherited from native C++ Object or create function:" .. classname)
        -- inherited from native C++ Object
        cls = {}

        if superType == "table" then
            -- copy fields from super
            for k, v in pairs(super) do
                cls[k] = v
            end
            cls.__create = super.__create
            cls.super = super
        else
            cls.__create = super
        end

        cls.ctor = function()
        end
        cls.__cname = classname
        cls.__ctype = 1

        function cls.new(...)
            local instance = cls.__create(...)
            -- copy fields from class to native object
            for k, v in pairs(cls) do
                instance[k] = v
            end
            instance.class = cls
            instance:ctor(...)
            return instance
        end
    else
        print("-- inherited from Lua Object:" .. classname)
        -- inherited from Lua Object
        if super then
            cls = clone(super)
            cls.super = super
        else
            cls = { ctor = function()end }
        end

        cls.__cname = classname
        cls.__ctype = 2 -- lua
        cls.__index = cls

        function cls.new(...)
            local instance = {}
            if(cls.super) then
                instance = cls.super:new(...)
            end
            setmetatable(instance, cls)
            for k, v in pairs(cls) do
                instance[k] = v
            end
            instance.class = cls
            instance:ctor(...)
            return instance
        end
    end
    _G[classname] = cls;
    package.loaded[classname] = cls
    setmetatable(cls, {__index = _G});
    setfenv(1, cls)
    return cls
end

做法也是比較簡單。建立例項的時候判斷有沒有基類,有的話呼叫建構函式,並設定基類物件元表為子類物件即可完美實現,之後我們建立類時可以直接這樣傳入基類名字即可

第二種類的實現方式是從網上看到的一位大神寫的,程式碼如下:

這種從概念上實現面向物件的方法做到以下幾點:

(1)有類定義和物件的概念,類定義通過new來建立物件,並且同時呼叫自己的建構函式

(2)子類可以訪問基類的成員函式

(3)類定義不能夠呼叫函式(除了new之外),只有物件才能呼叫函式

(4)建構函式呼叫有和c++一樣的層級關係,先呼叫父類的建構函式,再呼叫子類的建構函式

--lua面向物件:概念意義上的實現
local _class={}
 
function class(super)
    local class_type={}
    --注意:因為過載了__newindex函式, 所以ctor不要定義為nil
    class_type.ctor=false
    class_type.super=super
    class_type.new=function(...) 
            local obj={}
            --下面的塊只做了一件事:依次從父類到當前子類呼叫建構函式ctor
            do
                local create
                create = function(c,...)
                    if c.super then
                        create(c.super,...)
                    end
                    if c.ctor then
                        c.ctor(obj,...)
                    end
                end
 
                create(class_type,...)
            end
            setmetatable(obj,{ __index=_class[class_type] })
            return obj
        end
    
    --新加成員:防止定義類呼叫函式
    local vtbl={}
    _class[class_type]=vtbl
 
    setmetatable(class_type,{__newindex=
        function(t,k,v)
            vtbl[k]=v
        end
    })
 
    --只有定義類修改了__newindex
    --vbtl只屬於定義類
    --new出來的物件共享所有定義類的引用,但獨享自己新增加的成員變數
    if super then
        setmetatable(vtbl,{__index=
            function(t,k)
                local ret=_class[super][k]
                vtbl[k]=ret
                return ret
            end
        })
    end
 
    return class_type
end

使用方法;

base_type=class()		-- 定義一個基類 base_type
 
function base_type:ctor(x)	-- 定義 base_type 的建構函式
	print("base_type ctor")
	self.x=x
end
 
function base_type:print_x()	-- 定義一個成員函式 base_type:print_x
	print(self.x)
end
 
function base_type:hello()	-- 定義另一個成員函式 base_type:hello
	print("hello base_type")
end


----以上是基本的 class 定義的語法,完全相容 lua 的程式設計習慣。我增加了一個叫做 ctor 的詞,作為構----造函式的名字。
----下面看看怎樣繼承:

test=class(base_type)	-- 定義一個類 test 繼承於 base_type
 
function test:ctor()	-- 定義 test 的建構函式
	print("test ctor")
end
 
function test:hello()	-- 過載 base_type:hello 為 test:hello
	print("hello test")
end


--現在可以試一下了:

a=test.new(1)	-- 輸出兩行,base_type ctor 和 test ctor 。這個物件被正確的構造了。
a:print_x()	-- 輸出 1 ,這個是基類 base_type 中的成員函式。
a:hello()	-- 輸出 hello test ,這個函式被過載了

說明幾點:

(1)只有定義類修改了__newindex
(2)vbtl只屬於定義類
(3)new出來的物件共享所有定義類的引用,但獨享自己新增加的成員變數

有點:

(1)概念上更加清晰,熟悉c++面向物件的很容易瞭解這個繼承的關係

(2)寫法上感覺很牛逼,做到了定義不能呼叫函式這一點

(3)共享函式引用

缺點:

(1)概念上清晰的成本是要更多的時間去理解

(2)雖然做到了c++類定義和物件上的概念區別,但是還是有多東西沒有實現

(3)物件也可以定義自己的函式,這一點就直接打破了區分定義和物件的本源,但是價值還是有的

(4)所有new出來物件共享類定義的引用物件,包括不需要複用的函式和table。由此多個物件共享一個定義的table很是個問題!

* 針對(4),可以通過實現定義的init函式,在init函式給不同的物件初始化不同的資料,即使是table!

從實現上可以看到,在new一個物件的時候,沒有返回之前是沒有設定元表的,因此做到了類定義不能呼叫函式,不過這種寫法有利有弊吧,如果單純的實現一個功能的時候還是很不錯的做法,程式耦合度會很低,這裡給出我的使用例子,可以幫助理解

base_type=class()		-- 定義一個基類 base_type
 
function base_type:ctor(x)	-- 定義 base_type 的建構函式
	print("base_type ctor")
	self.x=x
end

function base_type:print_x()	-- 定義一個成員函式 base_type:print_x
	print(self.x)
	self.hello()
end

function base_type:init()
    print('==================')    
end
 
function base_type:hello()	-- 定義另一個成員函式 base_type:hello
	print("hello base_type")
end

輸出如下:

k name = print_x
k name = init
k name = hello
base_type ctor
==================

相信都能看懂!

參考:

http://www.cnblogs.com/pk-run/p/4248095.html