開篇廢話
最近重拾了Lua,因?yàn)樾马?xiàng)目在用。大概4-5年前準(zhǔn)備進(jìn)入從事游戲行的時(shí)候,看到某司的招聘簡章中提到了Lua,借暑假的時(shí)間看完了《Lua程序設(shè)計(jì)(第2版)》。還記得開始的時(shí)候,看到交換變量用一條語句實(shí)現(xiàn)就莫名的興奮,至今都沒搞清楚當(dāng)初為毛會興奮...。書中對Lua語言做了比較詳細(xì)的講述,強(qiáng)調(diào)了其靈活性,然而當(dāng)時(shí)處于似懂非懂的狀態(tài),沒什么體會。工作一段時(shí)間再拿起它,確實(shí)產(chǎn)生了共鳴,現(xiàn)在越來越喜歡這小巧的Lua。
Lua的美在于:它不提供其他語言那么多豐富的特性,但是它提供了自己實(shí)現(xiàn)這些特性的機(jī)制。
今天要寫的就是借助MetaTable,為Lua添加面向?qū)ο?/strong>的特性。開始之前寫上當(dāng)年為之興奮的一個(gè)行代碼~~
a, b = b, a
1 代碼
1.1 OO特性實(shí)現(xiàn)
此處的實(shí)現(xiàn)是從云風(fēng)大神那里借鑒,簡化產(chǎn)生的(Wiki原文)。由于個(gè)人資質(zhì)有限,對稍復(fù)雜的方案理解起來比較吃力,雖然原版代碼很短,但耗費(fèi)了挺長時(shí)間才理解。為了以后使用方便,根據(jù)自己的思維方式,出了這一版自認(rèn)為比較易于理解的實(shí)現(xiàn)。
-- 用于記錄所有類結(jié)構(gòu)
local _ClzTbl = {}
-- 索引基類成員的元表
local _BaseMeta = { __index = function(childClz, k)
if not childClz.base then return nil end
-- 變種點(diǎn):1
-- 將基類中的成員拷貝到子類中,可以提高二次訪問的效率(減少搜索的層次)
local v = childClz.base[k]
childClz[k] = v
return v;
end }
--- 定義類結(jié)構(gòu)
--- 約定:構(gòu)造函數(shù)使用名字Ctor
--- 注意:如果指定了基類base,則不能再為子類設(shè)置其他MetaTable.
---@param clzName string 類名,必須能全局唯一標(biāo)識一個(gè)類
---@param base table 要繼承的基類,沒有則不傳參
function class(clzName, base)
if type(clzName) ~= 'string' then return nil end
local clz = _ClzTbl[clzName]
-- 類結(jié)構(gòu)已經(jīng)定義過的,直接返回
-- 使用這種方式,避免在多次DoFile中被重復(fù)定義
if clz then return clz end
clz = {
Ctor = false,
base = base,
}
if base then
setmetatable(clz, _BaseMeta)
end
_ClzTbl[clzName] = clz
return clz
end
---變種點(diǎn):2
---遞歸調(diào)用構(gòu)造函數(shù)
---先調(diào)用基類的構(gòu)造函數(shù),再調(diào)用自己的
local _Create
_Create = function(clz, obj, ...)
if clz.base then
_Create(clz.base, obj, ...)
end
if clz.Ctor then
clz.Ctor(obj, ...)
end
end
--- 創(chuàng)建對象
---@param clz table 對象所屬的類,可以是類型的table也可以是類型的名字
---@param ... 要傳遞給構(gòu)造函數(shù)的參數(shù)列表
function new(clz, ...)
if type(clz) == 'string' then
clz = _ClzTbl[clz]
end
if not clz then return nil end
local obj = {}
setmetatable(obj, { __index = clz })
_Create(clz, obj, ...)
return obj
end
1.2 示例
-- 1.定義類
Base = class('Base')
function Base:Ctor(name)
self._name= name
print('This is Base....')
end
Child = class('Child', Base)
function Child:Ctor(name)
print('Old name:' .. self.__name)
self._name = name
print('New name:' .. self.__name)
end
-- 2.創(chuàng)建對象(兩種方式是等價(jià)的)
child1 = new (Child, 'Walker')
child2 = new ('Child', 'Walker')
2 簡介
此處實(shí)現(xiàn),沿襲了C#這類本身支持OO的語言的使用習(xí)慣,也有Python的影子。Lua中類、對象沒有任何區(qū)別,都是一個(gè)table而已。為了概念上的清晰,這里將其分開,視為不同的東西:class用于定義類結(jié)構(gòu),new用于創(chuàng)建對象。
2.1 class
用Table作為類結(jié)構(gòu),也就是生成對象的模板。
- 直接定義在類上的成員(函數(shù)、變量),都是在所有對象間共享的(此處和Python一樣)
- 類結(jié)構(gòu)中,用base變量引用一個(gè)基類結(jié)構(gòu),實(shí)現(xiàn)了單繼承。
- 通過__index元方法,實(shí)現(xiàn)了高效的基類成員檢索(繼承)
2.2 new(對象)
- 直接用元表完成對象結(jié)構(gòu)的創(chuàng)建
- _Create實(shí)現(xiàn)了C#的對象構(gòu)造順序(基類->子類)
- 在構(gòu)造函數(shù)Ctor中對self賦值的變量,才是成員變量(Python方式)。當(dāng)然由于動(dòng)態(tài)語言的特性,在任意位置給self賦值都有效。
3 變種
細(xì)心的朋友可能發(fā)現(xiàn)了,實(shí)現(xiàn)代碼中標(biāo)記了兩處變種點(diǎn)。這一版代碼實(shí)現(xiàn)的特性和云風(fēng)大神的基本一致,相當(dāng)于用一個(gè)更易理解的方式重構(gòu)了(至少對我自己更易理解)。順帶一提,目前項(xiàng)目中使用的就是云風(fēng)大神的原版。重構(gòu)完成再看的時(shí)候,發(fā)現(xiàn)這兩處可以用不同的方式,支持其他情況或用法。
3.1 變種點(diǎn)1
變種點(diǎn)1是在調(diào)用到基類成員的時(shí)候,會將該成員拷貝到子類的Table中。這種方式提高了二次檢索的效率,特別是繼承層次較深的時(shí)候。這是一種空間換時(shí)間的方案,當(dāng)內(nèi)存資源比CPU資源更稀缺的時(shí)候,它就不合適了。并且通常繼承層次都不會太深,在我看來Lua中繼承超過3層,設(shè)計(jì)上就很可能有問題了。
這里的修改方式非常簡單,直接__index=base就好了。(文件可以短10行了)
3.2 變種點(diǎn)2
_Create實(shí)現(xiàn)的對象構(gòu)造層次,是從最頂層的基類,依次調(diào)用到當(dāng)前子類,這是C#這類語言中使用的方式。后來想到了Python的方式,是否走基類的構(gòu)造由子類決定?,F(xiàn)在類結(jié)構(gòu)中記錄了base,刪掉_Create直接調(diào)用Ctor就是Python方式。(文件又可以短10多行了Orz....)
粗知漏見,歡迎指正。