標籤:style blog java color 使用 strong
物件導向的基礎是類,但Lua中沒有提供類的概念,所以我們需要利用Lua現有的機制來實作類別似於類的有關oop的一整套概念。基本方案是使用table來實作類別機制,並且結合使用self參數和冒號操作。我們先來看看self參數和冒號操作符的用法:
self參數的使用是很多物件導向語言的要點,大多數OO語言將這種機制隱藏起來,這樣程式員不必聲明這個參數(雖然仍然可以在方法內使用這個參數)。Lua也提供了通過使用冒號操作符來隱藏這個參數的聲明(通過冒號來聲明函數)。先來看下這段代碼(這裡面還不涉及到類的定義,只是純粹為瞭解釋self和冒號操作):
Account = {balance = 0}function Account.withdraw(obj, v) obj.balance = obj.balance + venda = Accounta.withdraw(a, 100)print(a.balance)
也可以將函數寫成這樣(將obj換成self能更好的表達出該參數的語義):
function Account.withdraw(self, v) self.balance = self.balance + vend
self本身是一個Lua的保留字,為了不必給每個函式宣告都加上self,Lua提供了冒號操作符:
function Account:withdraw (v) self.balance = self.balance - vend
定義函數時將點改成冒號,函數體內部直接用self參數訪問對象自己(self參數表示表自己,也就是對象本身)。同時函數調用也有兩種寫法,並且這兩種寫法是通用的:
a.withdraw(a, 100)a:withdraw(100)
點操作需要顯示傳入調用者自身。實際上,冒號的效果相當於在函數定義和函數調用的時候,增加一個額外的隱藏參數。這種方式只是提供了一種方便的文法,並沒有什麼新的內容。我們可以使用dot文法定義函數而用冒號文法調用函數,反之亦然,只要我們正確的處理好額外的 參。
Lua中的表其實是一個對象。我們定義一個表作為原型(相當於類定義),然後其他的對象(也是一個表)通過metatable和__index機制來訪問原型表中的成員。下面看下類的實現:
Account = {balance = 0}--將表自己做成metatable,或者也可以在表中加入一個mt成員來作為metatablefunction Account:new(o) o = o or {} setmetatable(o, self) self.__index = self return oendfunction Account:withdraw(v) self.balance = self.balance - vendfunction Account:deposit(v) self.balance = self.balance + venda1 = Account:new{}a2 = Account:new{balance = 100}a1:withdraw(100)print(a1.balance)print(a2.balance)
為了簡潔,直接將類本身定為對象表的metatable。上面的new函數還是有一定的技巧性的,之前用過C++/Java等語言的人,應該不太習慣這種寫法。因為在強型別語言中,編程者會盡量避免傾向於展示技巧的寫法,而使用更簡單、可讀性更高的寫法。但在使用Lua時需要轉換一下思維,尤其是在基於Lua基礎的東西來擴充一些概念的時候,各種技巧性的東西會很常見,要習慣。
類繼承的實現其實很簡單,直接調用基類的new操作,然後在子類中可以添加一些新的成員或方法:
-- 繼承自Account的類(其實就是一個Account對象),SpecialAcount可以重新定義繼承來的成員函數,或添加新的成員函數SpecialAccount = Account:new()-- 子類執行個體化,有趣之處是,s的metatable是SpecialAccounts = SpecialAccount:new{limit = 1000}
可以看到,其實上面的類也是對象,只是看你怎麼去理解它,當把一個對象當做模板原型來用時,它就是一個類,但同時,它也是一個對象,很隨意有木有。
多繼承:多繼承的想法很簡單,就是對於子類中不存在的域,在多個父類中依次尋找即可。不過此代碼有點複雜,如下所示:
Named = {name = "default"}function Named:getname() return self.name endfunction Named:setname(v) self.name = v end--k為函數名,list為父類列表search = function(k, list) for i = 1, table.getn(list) do local v = list[i][k] -- 訪問第i個父類的k域,存在則返回 if v then return v end endend-- 參數為父類列表createClass = function (...) c = {} -- 這裡面的k為訪問的域 setmetatable(c, {__index = function(t, k) return search(k, arg) end}) -- 用作對象的metatable c.__index = c function c:new(o) o = o or {} setmetatable(o, c) return o end return cendNamedAccount = createClass(Account, Named)obj = NamedAccount:new{name = "Paul"}print(obj:getname())
函數search表示在父類列表list中依次尋找名字為k的域。
函數createClass用於建立多繼承的類,參數為父類列表。可以看到對象以多繼承類為metatable,而多繼承類通過metatable在多個父類中依次尋找域。