物件導向教學 |
|
local MyClass = {} -- the table representing the class, which will double as the metatable for the instances MyClass.__index = MyClass -- failed table lookups on the instances should fallback to the class table, to get methods -- syntax equivalent to "MyClass.new = function..." function MyClass.new(init) local self = setmetatable({}, MyClass) self.value = init return self end function MyClass.set_value(self, newval) self.value = newval end function MyClass.get_value(self) return self.value end local i = MyClass.new(5) -- tbl:name(arg) is a shortcut for tbl.name(tbl, arg), except tbl is evaluated only once print(i:get_value()) --> 5 i:set_value(6) print(i:get_value()) --> 6
首先,我們建立一個表格來表示類別,並包含它的方法。我們也可以同時把它作為實例的元表格使用,不過如果你喜歡,也可以使用獨立的實例元表格。
在建構式中,我們建立實例(一個空的表格),賦予它元表格,填入欄位並傳回新的實例。
在方法中,我們使用「self」參數來取得要操作的實例。這種用法相當常見,因此 Lua 提供了 :
的語法糖,它可以從表格中呼叫函式項目,並在第一個參數之前插入表格本身。
我們可以進行一些改進
local MyClass = {} MyClass.__index = MyClass setmetatable(MyClass, { __call = function (cls, ...) return cls.new(...) end, }) function MyClass.new(init) local self = setmetatable({}, MyClass) self.value = init return self end -- the : syntax here causes a "self" arg to be implicitly added before any other args function MyClass:set_value(newval) self.value = newval end function MyClass:get_value() return self.value end local instance = MyClass(5) -- do stuff with instance...
我們在類別表格中加入一個包含 __call
元方法的元表格,這個元方法會在一個值被當成函式呼叫時觸發。我們讓它呼叫類別的建構式,因此你在建立實例時不需要使用 .new
。另一個選擇是直接在元方法中放入建構式。在元方法中,"cls" 會指向目前的表格。
此外,為了搭配 :
方法呼叫捷徑,Lua 讓你可以在表格中定義函式時使用 :
,它會隱式地加入 self
參數,讓你不用自己輸入。
我們可以輕鬆地擴充上述範例中類別的設計,使用繼承
local BaseClass = {} BaseClass.__index = BaseClass setmetatable(BaseClass, { __call = function (cls, ...) local self = setmetatable({}, cls) self:_init(...) return self end, }) function BaseClass:_init(init) self.value = init end function BaseClass:set_value(newval) self.value = newval end function BaseClass:get_value() return self.value end --- local DerivedClass = {} DerivedClass.__index = DerivedClass setmetatable(DerivedClass, { __index = BaseClass, -- this is what makes the inheritance work __call = function (cls, ...) local self = setmetatable({}, cls) self:_init(...) return self end, }) function DerivedClass:_init(init1, init2) BaseClass._init(self, init1) -- call the base class constructor self.value2 = init2 end function DerivedClass:get_value() return self.value + self.value2 end local i = DerivedClass(1, 2) print(i:get_value()) --> 3 i:set_value(3) print(i:get_value()) --> 5
我們有一個衍生類別表格和一個 __index
元方法,讓它可以繼承基底類別。另外,我們把實例的建立移到 __call
元方法中,並把建構式變成純粹的初始化方法。這樣一來,衍生類別可以在自身呼叫基底類別的初始化函式。
最後一個可用來進行優化的步驟是將基底類別的內容複製到衍生類別中,而不是使用 __index
。這樣可以避免 __index
長長的鏈條,這可能會讓方法呼叫變慢,另外,如果基底類別有像 __add
這類的方法,它們也會像衍生類別上的適當元方法一樣運作。這是因為在尋找元方法時,並不會遵循 __index
。
local DerivedClass = {} for k, v in pairs(BaseClass) do DerivedClass[k] = v end DerivedClass.__index = DerivedClass
知道了這些之後,我們就可以建立一個便利函式來建立類別,它可以選擇從其他類別繼承。以下是一個這樣的函式範例
function (...) -- "cls" is the new class local cls, bases = {}, {...} -- copy base class contents into the new class for i, base in ipairs(bases) do for k, v in pairs(base) do cls[k] = v end end -- set the class's __index, and start filling an "is_a" table that contains this class and all of its bases -- so you can do an "instance of" check using my_instance.is_a[MyClass] cls.__index, cls.is_a = cls, {[cls] = true} for i, base in ipairs(bases) do for c in pairs(base.is_a) do cls.is_a[c] = true end cls.is_a[base] = true end -- the class's __call metamethod setmetatable(cls, {__call = function (c, ...) local instance = setmetatable({}, c) -- run the init method if it's there local init = instance._init if init then init(instance, ...) end return instance end}) -- return the new class table, that's ready to fill with methods return cls end
我們也可以使用閉包來製作物件。實例建立的速度較慢,而且使用較多的記憶體,不過它也有一些優點(例如更快的實例欄位存取),而且是閉包如何應用的有趣範例。
local function MyClass(init) -- the new instance local self = { -- public fields go in the instance table public_field = 0 } -- private fields are implemented using locals -- they are faster than table access, and are truly private, so the code that uses your class can't get them local private_field = init function self.foo() return self.public_field + private_field end function self.bar() private_field = private_field + 1 end -- return the instance return self end local i = MyClass(5) print(i.foo()) --> 5 i.public_field = 3 i.bar() print(i.foo()) --> 9
請注意,.
語法用於呼叫方法,而非 :
。這是因為 self 變數已經儲存在方法中,作為上值,因此呼叫它的程式碼不需要傳遞它。
也可以以這種方式繼承
local function BaseClass(init) local self = {} local private_field = init function self.foo() return private_field end function self.bar() private_field = private_field + 1 end -- return the instance return self end local function DerivedClass(init, init2) local self = BaseClass(init) self.public_field = init2 -- this is independent from the base class's private field that has the same name local private_field = init2 -- save the base version of foo for use in the derived version local base_foo = self.foo function self.foo() return private_field + self.public_field + base_foo() end -- return the instance return self end local i = DerivedClass(1, 2) print(i.foo()) --> 5 i.bar() print(i.foo()) --> 6
基於表格的優點
MyClass.method(instance, args)
)。:
呼叫方法與絕大多數物件導向 Lua 程式碼更一致。基於封閉的優點
__index
元方法。.
方法呼叫語法比較熟悉。