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
==================
相信都能看懂!
參考: