日期:2014.7.14 ? ??
PartⅡ Object-Oriented Programming
Lua中實現面向對象編程。
“如同OOP對象,table擁有狀態;如同OOP對象,table擁有標識符---self,用來與其他變量做區分,而且兩個table擁有同樣的值也是不同的object(對象),因為self的不同;如同OOP對象,table也有生命周期,這個生命周期與誰在何處創建table是保持獨立的”
對象是擁有自己的運算操作的,table也有,如
e.g.
Account = {blanche = 0}
function Account.withdraw(v)Account.balance = Account.balance - v
end
上述的函數就是OOP中稱呼的method(方法)。當然,上述的使用技巧是不可取的:在函數體內使用全局變量Account。這樣會造成嚴重的后果,而且這樣使用限制性太大,當我們改變了變量類型,這個操作就失效了。這種操作與面向對象編程中對象保持獨立的生存周期這一原則相悖。
e.g.
a , Account = Account,nil
a.withdraw(100.00) --error
因為我們將Account賦值為nil了,所以withdraw函數就會報錯。
針對上述的操作改進:我們可以傳遞額外的參數,作為函數運算的對象
e.g.
function Account.withdraw(self,v)self.balance = self.balance - v
end
此時
a , Account = Account,nil
a.withdraw(100.00) --ok
但是在大多數面向對象編程的語言中,一般都是隱藏我們上述用到的那個參數。Lua也能隱藏這個參數,這里就要使用到冒號操作符:
e.g.
function Account:withdraw(v)self.balance = self.balance - v
end
當然,這里使用冒號操作符只是一個語法約定而已,沒有額外的意思。我們可以用點號運算符定義一個函數然后用冒號運算符調用該函數,反之亦然
e.g.
Account = {balance = 0,withdraw = function (self,v)self.balance = self.balance - vend}
function Account:deposit (v)self.balance = self.balance + v
end
Account.deposit(Account,200)
我個人還是覺得按套路來,遵循這種語法約定。
16.1 Classes
Lua中沒有類(class)的概念,但是很容易模仿出類。參考了prototype-base language(面向原型編程)中prototype(原型)的相關技巧。在這種語言中,也是沒有類,但是每個對象都擁有一個原型。在這種語言環境下要表現出類的概念,我們只需要為繼承者創建一個唯一的對象作為原型。類和原型的目的都在于共享某些行為。
前面在討論元表的時候有提到繼承,因此假如現在有兩個對象a和b,采用如下操作便可將b設置為a的原型:
e.g.
setmetatable(a,{__index = b})
執行了這個操作之后,假如我們訪問a中的成員,在找不到的時候會訪問b。
回到現在討論的類,假如我們需要創建一個新的account,其行為與Account一樣,在這里我們就可以考慮使用繼承,使用 __index 元方法。在這里我們不需要額外創建一個新的table作為元表,可以直接將我們要繼承的table設置為其元表:
e.g.
function Account:new(o)o = o or {}setmetatable(o,self)self.__index = selfreturn o
end
這里使用到了前文提到的冒號操作符,默認使用了self參數。
此時
a = Account:new(balance = 0}
a:deposit(100.00)
我們新建了一個table ?a,其元表為Account,又修改了其元方法__index 為Account 自身,當我們在a中尋找deposit的時候,找不到的時候會自動在Account中尋找,達到了繼承的要求。
創建a的時候,將balance賦值為了0,假如不給其賦值,則會繼承其默認值
b = Account:new()
print(b.balance) --- 0 繼承了Account的balance的值0
16.2 Inheritance
繼承
Lua中實現繼承還是比較容易的
e.g.
--基類
Account = {balance = 0 }
function Account:new(o)o = o or {}setmetatable(0,self)self.__index = selfreturn o
end
function Account:deposit(v)self.balance = self.balance + v
end
function Account:withdraw(v)if v > self.balance then error "xxx" endself.balance = self.balance - v
end
現在我們想寫一個子類繼承這個基類,然后能在子類中做進一步的修改,可以這樣操作
SpecialAccount = Account:new()
執行以上操作之后,SpecialAccount 便是Account的一個實例了(--modify 應該是繼承而非實例吧?),當我們執行一下操作:
s = SpecialAccount:new(limit = 1000.00}
SpecialAccount 從基類中繼承了new這個方法,因為這里使用了冒號操作符,默認使用了SpecialAccount這個參數,因此此時s的元表是SpecialAccount。當我們試圖訪問s中不存在的元素的時候,便會去SpecialAccount中尋找,而從SpecialAccount中尋找不到的時,轉而會去Account中尋找。
e.g.
s:deposit(100.00)
此時lua會在s、SpecialAccount、Account里面尋找deposit方法
我們可以在子類中重新定義從基類中繼承的方法:
e.g.
function SpecialAccount:withdraw(v)if v - self.balance >= self.getLimit() thenerror"xx"endself.balance = self.balance - v
end
function SpecialAccount:getLimit()return self.limit or 0
end
此時,當我們調用s:withdraw的時候,lua會直接在SpecialAccount找到該方法,執行該方法內的操作。
而lua中有趣的一點是,不需要重新創建一個新的類來實現一個新的行為,可以直接在對象中實現該行為,如:
上文我們已經創建了SpecialAccount對象s,我們要在s中實現一個限制行為,限制每次的操作限額,我們可以這樣實現:
e.g.
function s:getLimit()return self.balance * 0.10
end
這樣,當我們調用s:withdraw的時候,條件判斷getLimit會直接得到s已經定義的行為,而不會再去SpecialAccount中尋找。
16.3 Multiple Inheritance
多重繼承
Lua中實現面向對象編程是有很多種途徑的,上文中提到的使用 __index 元方法是一種便捷的方式。在不同的情況下需要選擇不同的實現方式,在這里介紹的是一種能實現多重繼承的方法。
這里也涉及到了使用__index 元方法,在該方法內使用一個函數。當table的元表的 __index 字段中有一個函數的時候,Lua都會調用該函數而不管有沒有在該table中尋找到key。
多重繼承的思想在于一個類可以有多個父類。因此我們就不能用類的方法來創建子類,而是定義一個函數來實現該功能--createClass,以父類作為參數來創建子類。這個函數創建一個table來代表新的類,然后設置元表的元方法__index 來實現多重繼承。在這里有要注意的地方,類和父類的關系與類和實例的關系是有差異的,一個類不能同時成為其實例和其子類的元表。
e.g.
--假定現在有兩個類,之前的Account和現在的Named
Named = {}
function Named:getname()return self.name
end
function Named:setname(v)self.name = v
end--在plist這個table中尋找k
local function search(k,plist)for i = 1,#plist dolocal v = plist[i][k]if v then return v endend
endfunction createClass(…)local c = {} --新的類local parents = { … }--從父類table中找到各個父類中的方法setmetatable(c,{ __index = function (t,k)return search(k,parents)end} ) --多重繼承的技巧在于此處,__index 元方法是一個函數,該函數會從父類列表中尋找每個父類中的所有方法,這樣就實現了多重繼承--新的類成為其實例的元表c.__index = c--創建新的類的構造方法function c:new(o)o = o or {}setmetatable(o,c)return oendreturn c
end
現在我們就能創建一個多重繼承的類了:
--多重繼承,創建新的類
NamedAccount = createClass(Account,Named)
--創建和使用實例
account = NamedAccount:new{name = "abcd"}
print(account:getname())
上述的search函數一定程度上影響性能,以下是作者給的改進:
setmetatable(c,{ __index = function ( t,k )local v = search(k,parents)t[k] = vreturn vend})
一種編程技巧,謹記!
16.4 Privacy
隱私
在已提到的對對象的設計中,并沒有提供隱私機制。這是我們使用table來表現對象的結果,也是受影響與Lua本身排斥一些冗余、人為限制的功能。作者的建議是假如不想訪問某些值,那么大可以不去訪問就是。
Lua的目標是為開發者提供便利,提供多種技巧實現多數需求,盡管設計lua中的對象初衷是不提供隱私機制的,但是可以通過別的方法來實現這個需求——訪問控制。這個用的比較少,但還是值得去了解和學習掌握的。
實現這個功能需求在于用兩個table來表現對象:一個表示其狀態,一個用來表示其操作行為。訪問對象的時候通過第二個table進行訪問,而對第一個table的設計也有一定的要求,該table并不是存儲在別的table中,而是存儲在該對象方法的closure中。以此重新設計Account
e.g.
function newAccount( initialBalance )local self = {balance = initialBalance}local withdraw = function ( v )self.balance = self.balance + vendlocal getBalance = function ( ... )return self.balanceendreturn{withdraw = withdraw,deposit = deposit,getBalance = getBalance}
end
在這里該函數首先創建了一個table用來存儲內部對象的狀態,存儲至一個局部變量self。然后該函數內部創建了對象的一系列方法。最后函數創建并返回了另外一個對象,該對象內部存儲了實際上要實現的方法的名字。返回的這個新的table應該相當于上文提到的第二個table。這里的核心點在于:這些方法沒有使用冒號操作符得到self這個額外的默認參數,而是直接使用了。現在我們可以以一下方式創建新的對象并使用其方法:
e.g.
acc1 = newAccount(100.00)
acc1.withdraw(40.00)
print(acc1.getBalance())
利用這種方式創建的table,我們是沒有辦法直接訪問原table的,只能通過newAccount里面的方法來訪問。這樣就實現來我們想要的隱私功能。
16.5 The Single-Method Approach
單例的實現
e.g.
print("The Single-Method Approach \n")
function newObject( value )return function ( action,v )if action == "get" then return valueelseif action == "set" then value = velse error("invalid action")endend
endd = newObject(0)
print(d("get"))
d("set",10)
print(d("get"))
沒有實例,直接通過對象本身訪問對象實現的方法。
?