PS:請注意,由於網頁顯示的關係,以下_setmetatable這個函式請把_拿掉。_不應該出現
其實呢,Lua裡面的Table就是一個物件了。而且他可以有屬於自己的方法(method),如以下範例:
Acount = {balance = 0} function Account.withdraw (value) Account.balance = Account.balance - value end實行的時候,直接採用Account.withdraw(100.00)就可以了。
但是呢,這樣的寫法其實不好,因為上述方法裡面直接去修改了Account.balance的數值,這樣是不好的方式。因此呢,可以改用以下方式:
Account = {balance = 0} function Account:withdraw (value) self.balance = self.balance - value end以上這樣的寫法,如同:
function Account.withdraw(self, value) self.balance = self.balance - value end所以,冒號的用法,是隱藏了一個運算function,他會將物件本身self給傳遞進去,這樣一來,即使你的物件不叫做Account也可以做這些方法喔。例如:
Account2 = Account; Account = nil Account2:withdraw(100.00)這樣就ok了。
不然你自己傳遞進去也可以,例如:
Account2.withdraw(Account2, 100.00)也是一樣的結果。
OK,有了table之後,來看看怎樣建立一個類別吧(Class)。
類別是創造物件的一個模具,也就是說,依照這樣的模具(類別)去建立一個物件,且具有其類別的特性(狀態)與方法(行為)。在Lua中其實沒有類別的概念,但是,可以透過table特有的metatable去模擬類別。例如,要讓a去實現b, b是a的原型:
_setmetatable (a, {__index = b})
這樣的作法,未來即使在a中沒有的特性或方法,他會往b去找尋。因此,達到繼承的概念。
以銀行帳戶為一個物件之觀念,以下為範例:
Account = {balance = 0, withdraw = function (self, value) self.balance = self.balance - valve end } function Account:deposit(value) self.balance = self.balance + value end因此在建立一個新物件的時候,會採用:
function Account:new(Object) Object = Object or {} -- 如果沒有輸入任何資料,則建立一個空table _setmetatable(Object, self)-- 設定Object物件的metatable是Account (Self = Account) self.__index = self -- Account的metatable是他自己 return Object -- 回傳這個物件 end<感謝ykhuang建議與補充,物件的實作是以metatable加上metatable的metamethod來完成,因此上面範例中,是以Account作為metatable,其metamethod __index是以Account本身作為未來查詢索引根據>
因此,我要建立一個帳號的物件:
Big = Account:new({balance = 0})然後呢,我就可以直接使用Account這個類別的方法了。
Big:deposit(100.00)
上述例子,我在解釋一遍:
當創建新的Account的時候,Big這個物件將Account作為他的metatable。因此,當要使用deposit這個方法的時候,雖然在Big裡面找不到,但是因為Big繼承了Account(以Account作為metatable),其metamethod__index是以Account作為查詢的依據,所以lua會往Account的方法裡面去找尋。所以,Big就可以使用deposit這個方法。當然,也繼承了Account.balance這個特性。
但是,如果再Big這個帳戶裡面的deposit方法跟Account不一樣怎麼辦呢?很簡單,你就直接覆蓋過去就好,例如:
function Big:deposit(value) self.balance = self.balance - value - 100 end這樣,當Big要使用deposit這個方法,他會先在Big裡面找到方法,因此Account這個deposit就被覆蓋掉了。
在Lua裡面當然也可以多方繼承,但是就會比較麻煩一點囉。在Lua裡面,物件導向並不是原生的,所以,在多方繼承方面,並沒有這麼直接,而且有一點點的~”取巧”。該怎麼說呢,前面提到,當要繼承一個類別的時候,他直接承接了類別的所有特性與方法,這是透過__index去達成。但是,當要繼承多個類別的時候,就必須在__index裡面,建立一個function,然後去搜尋每一個被繼承的類別裡面有沒有這樣的方法存在。
例如:
local function search (k, plist) for i = 1, #plist do local v = plist[i][k] if v then return v end end end function createClass(...) local NewObject = {} local parents = {...} _setmetatable( NewObject, {__index = function (t, k ) return search (k, parents) end}) NewObject.__index = NewObject function NewObject:new(Object) Object = Object or {} _setmetatable(Object, NewObject) return Object end return NewObject end那怎麼使用勒?看看下面:
假設有兩個要被繼承的物件,一個是上面的Account,一個是Named:
Named = {} function Named:getname() return self.name end function Named:setname(n) self.name = n end --所以,直接調用createClass: NamedAccount = createClass( Account, Named ) --就這樣,NamedAccount繼承了Account以及Named --所以可以新建立一個新帳戶 account = NamedAccount:new({name = “Big”}) print(account:getname()) -- > Big是不是就辦到了物件導向功能了呢?
蝦米,上面看不懂,好唄,我來解釋一下:
首先,我們有Account以及Named兩個物件,然後希望用一個account去繼承這兩個所含有的特性與方法。所以,要建立一個多重繼承的”建立類別”的function ==> createClass(Account, Named)
在createClass裡面,先建立一個NewObject = {},然後,parent是把輸入近來所有要被繼承的table(採用...代表所有輸入參數),然後設定NewObject的metatable是一個搜尋函數,搜尋輸入進去的table是否有被呼叫的方法。然後把這個NewObject的metatable為他自己。然後就可以建立一個新物件了。所以,new出來的這個物件也就有NewObject這個物件的特性與方法,而NewObject這個物件也是繼承了你輸入進去的物件(Account與Named)。
所以,當要使用account:getname()的時候,系統會去找account有沒有getname的方法,若沒有,就往account的上一層NamedAccount去找,那在NamedAccount繼承了Account以及Named,所以會在Named裡面找到getname的方法,就可以使用了。
但是由於每一次呼叫getname,系統都要去metatable裡面翻找,會耗系統速度。所以setmetatable()方法可以修改如下,以增加速度:
_setmetatable( NewObject, {__index = function (t, k ) local v = search(k, parents) t[k] = v --把找到的函數保存在自己(t=self),以待下次使用 return v end})
一點意見,物件的實作是以metatable加上metatable的metamethod來完成,所以這個範例
回覆刪除function Account:new(Object)
Object = Object or {}
_setmetatable(Object, self)
self.__index = self -- Account的metatable是他自己
return Object
end
建議解釋為 "Account作為metatable時,其metamethod __index以Account本身作為索引根據" 較不會混淆
另外 Big = Account:new(balance = 0) 少了括號,應該是 Big = Account:new({balance = 0})
Dear ykhuang:
回覆刪除非常感謝你的建議與糾正。若我還有錯誤的地方,歡迎跟我聯絡。希望能把這語言推廣出去。
big
大家互相啦,過陣子我應該也會開始認真的使用corona了,到時候可能還需要請教^^
回覆刪除Account = {balance = 0, withdraw = function (self, value)
回覆刪除self.balance = self.balance - valve
end }
請問這種宣告法,裡面的withdraw適合用途,可以怎樣運用呢?