t = {}
print(getmetatable(t)) --> nil
t_mt = {} -- 建立 table 作 metatable 用
setmetatable(t, t_mt) -- 設置 t 的 metatable 是 t_mt
以下我們示範兩個 table 相加
Set = {}
Set.mt = {} -- 建立 metatable 給 Set
function Set.new (t)
local set = {}
setmetatable(set, Set.mt) -- 設置 metatable
for _, l in ipairs(t) do set[l] = true end
return set
end
s1 = Set.new{10, 20, 30, 50}
s2 = Set.new{30, 1}
print(getmetatable(s1)) --> table: 0x7f953bd03f20
print(getmetatable(s2)) --> table: 0x7f953bd03f20
Set.mt.__add = function (a,b) -- 實作宇集(union) __add metamethod
local res = Set.new{}
for k in pairs(a) do res[k] = true end
for k in pairs(b) do res[k] = true end
return res
end
print_r(s1 + s2) -- table: 0x7fcc855f2e10 {[1] => true, [30] => true, [10] => true, [50] => true, [20] => true}
print_r 不清楚請參考前一篇 Corona SDK Lua 程式語言基礎
metamethod 不只有 __add 可以實作, 還有 __mul(乘)、__sub(減)、__div(除)、__unm(負)、__pow(冪)、__eq(等於)、__lt(小於)、__le(小於等於)...等,較常使用應該是__tostring 跟物件導向設計要使用的 __index。下面先介紹 __tostring,隱含物件轉字串實作:
Set.mt.__tostring = function (set)
return "[Set Object]"
end
print(s1,s2) --> [Set Object] [Set Object]
接下來介紹 __index。當我們存取一個表不存在的變數時,返回結果應為nil,這是正確的,但並不完全正確。實際上,這種存取變數時會觸發 lua 解釋器去查找 __index metamethod:如果不存在,返回結果為nil;如果存在則由__index metamethod 返回結果。延續上面範例:
print(s1.width) --> nil (其實有觸發預設 __index,且回傳 nil)
Set.mt.__index = function() -- 覆寫 __index 方法
return "觸發__index"
end
print(s1.width) -->觸發__index (建立預設值的概念)
--所以無論如何存取 table 未定義變數時都一定會隱含執行 __index metamethod
我們可以發現 __index 可以用來作為物件導向的繼承概念,給予繼承物件預設值(子物件屬性會繼承父物件屬性)。假設我們想建立一些表來描述窗戶物件。每一個表必須描述窗戶物件的一些參數,比如:位置,大小,顏色風格等等。所有的這些參數都有預設的值,當我們想要建立窗戶物件的時候只需要給出非預設值的參數即可建立我們需要的窗戶物件。首先,我們實作一個原型和一個構造函數,他們共享一個 metatable:
-- 建立名稱空間
Window = {}
-- 建立 prototype 變數
Window.prototype = {x=0, y=0, width=100, height=100}
-- 建立 metatable
Window.mt = {}
-- 宣告建構子
function Window.new (o)
setmetatable(o, Window.mt)
return o
end
-- 建立預設值來自 prototype 變數
Window.mt.__index = function (table, key)
return Window.prototype[key]
end
-- 創造 Window 物件
w = Window.new{x=10, y=20}
print(w.width) --> 100 (來自 prototype 的變數)
特殊用法:Window.mt.__index,當他是一個表的時候,Lua 將在這個表中看是否有缺少的變數。所以,上面的那個例子可以使用第二種方式簡單的改寫為
Window.mt.__index = Window.prototype --跟用函示意思一樣但寫法簡潔
w = Window.new{x=10, y=20}
print(w.width) --> 100 (相當執行 Window.prototype["width"])
接下來進入我們正式主題物件導向設計首先我們要改變 table 中函示的宣告方式:
T = {val=1}
function T.func(v)
return T.val + v
end
--第一次改寫,用 self 程式更有彈性
function T.func(self, v)
return self.val + v
end
--第二次改寫,隱藏第一個函示參數 self 用:
function T:func(v)
return self.val + v
end
在Lua中,使用前面我們介紹過的繼承的思想,很容易實現prototypes。更明確的來說,如果我們有兩個對象 a 和 b,我們想讓 b 作為 a 的 prototype 只需要
setmetatable(a, {__index = b})
這樣,當對象 a 使用任何不存在的成員都會到對象 b 中查找。術語上,可以將 b 看作類別,a 看作物件。我們來設計建構子:
function T:new (o)
o = o or {}
setmetatable(o, self) --優化程式,讓本身 T 作為 metatable
self.__index = self --讓本身 T 表內所有參數被 o 繼承。這樣子的話,一個類不僅提供方法,也提供了他的實例成員的預設值
return o
end
嘗試建立物件:
local t = T:new({val=2})
print(t.val) -- 2,因為直接設 val,不會再去找 __index 表的 val
print(t:func(10)) -- 12,t 未定義 func 屬性,但是解析器會找查 metatable 的 __index 表,所以使用了 getmetatable(t).__index.func(T,v) 意即 T:func(v)
所以類的設計跟使用方法長這樣:
--MyOOP.lua
--成員屬性設定
MyOOP = {
a = 1,
b = 2
}
--私有函示設定
local function privateFunc()
print("123")
end
--公開函示設定
function MyOOP:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function MyOOP:func1()
print("func1")
end
function MyOOP:func2()
print("func2")
--privateFunc()
end
return MyOOP
--main.lua
local MyOOP = require("MyOOP")
local oop = MyOOP:new()
oop:func1()
呈上例,接下來談繼承,很簡單,設置變數 ExtendMyOOP 實例化 MyOOP:new(),再定義子類 ExtendMyOOP 新方法或覆寫函示即可。
ExtendMyOOP = MyOOP:new()
function ExtendMyOOP:func1()
print("extend func1")
end
local e = ExtendMyOOP:new{b=1000.00} --當 new 執行的時候,self 參數指向 ExtendMyOOP。所以,e 的 metatable 是 ExtendMyOOP,__index 也是 ExtendMyOOP。
e:func1() --> extend func1 (直接使用覆寫方法,不會再找 __index)
e:func2() --> func2 (Lua 在 e 中找不到 func2 函示,它會到 ExtendMyOOP 中查找,在 ExtendMyOOP 中找不到,會到 MyOOP 中查找)
print(e.a) --> 1 (Lua 在 e 中找不到 a 變數,它會到 ExtendMyOOP 中查找,在 ExtendMyOOP 中找不到,會到 MyOOP 中查找)
print(e.b) --> 1000 (直接使用覆寫變數,不會再找 __index)
有任何問題歡迎留言~官方範例
沒有留言:
張貼留言