使用事件表建立類別和方法

lua-users home
wiki


[!] VersionNotice: 下列程式碼屬於較舊的 Lua 版本,Lua 4。在 Lua 5 中無法照樣執行。自從完成 Lua 5.0 後,此頁面已過時。請參閱 LuaClassesWithMetatable

Lua4.0 與 Lua4.1 (work4) 之間的差異就像 AWK 與 Perl 5 之間的差異。新的 metatable 和字彙範圍對於使用 Lua 建立類別來說非常棒,但需要對標籤方法有一些了解。

注意:在 Lua4.1 (work4) 中,metatable() 指令已變更為 metatable() -- Dominik Wagner

注意:我已更改文章以反映此變更。-- JamesHearn

若要了解標籤方法,請先將建取事件和設定事件設定為 print 函數。這樣您就可以看到在表格使用發生時,傳遞給事件函數的是哪些引數。

$ lua
Lua 4.1 (work4)  Copyright (C) 1994-2001 TeCGraf, PUC-Rio
> t = { 11,22,33 , one = "a", two = "b", three = "c" }
> foreach(t,print)
1       11
2       22
3       33
one     a
three   c
two     b
> mt = { gettable = print, settable = print }
> metatable( t, mt )
> x = t[2]
table: 0x80631d0        2
> = t
table: 0x80631d0
> x = t.three
table: 0x80631d0        three
> x = t["three"]
table: 0x80631d0        three
> t.one = 'rat'
table: 0x80631d0        one     rat
> t[1] = 99
table: 0x80631d0        1       99
> = x
nil
>
從中,我們可以看到,當您嘗試在表格「t」中查找值時,建取方法會將表格「t」和索引作為引數傳遞。我們也可以看到,當您嘗試將表格成員設定為值時,設定方法會將表格「t」、索引和新值傳遞。

設定事件和建取事件很直接,但索引事件更有用。當您嘗試從表格中取得表格沒有的值時,就會呼叫索引事件。對於這些情況,您可以做點特別的事,例如在另一個表格或您想要的任何位置查看。

> foreach(t,print)
1       11
2       22
3       33
one     a
three   c
two     b
> mt = { index = print }
> metatable( t, mt )
> x = t.fish
table: 0x80631d0        fish
> = t
table: 0x80631d0
> x = t[44]
table: 0x80631d0        44
> x = t['zzz']
table: 0x80631d0        zzz
> = x
nil
>
我們可以看到索引事件會取得表格「t」和索引,就像建取事件所做的一樣,但請將索引事件視為未定義表格值的特殊建取事件。請注意,如果您使用建取事件,則永遠不會呼叫索引事件。

Lua 4.1 (work4) 讓您可以將這些標籤方法設定為函數或其他表格進行存取。

> foreach(t,print)
1       11
2       22
3       33
one     a
three   c
two     b
> u = { cow = 'big' }
> mt.index = u
> foreach(u,print)
cow     big
> = t.cow
big
>

以下就是您製作類別時所需的所有內容,就像您會在 Perl 中製作一樣。

類別的物件只是一個表格,其 meta-table 設定為另一個表的類別。

您可以將所有類別方法放入類別表格並將索引方法設定為類別表格。請注意以下我的「類別」函數是如何將其索引成員設定為它本身的。

然後,當您執行類別呼叫,例如 t:fun(4),這是 t.fun(t,4) 的簡寫,如果表格「t」沒有函數「fun」,它將使用索引方法在類別表格中查看。

------------------------------------------------------------------------------
--
function class( t )
  t = t or {}
  t.index = t
  t.new = new
  t.copy = copy
  return t
end

------------------------------------------------------------------------------
function new( class, init )
  init = init or {}
  return metatable( init, class )
end

------------------------------------------------------------------------------
function classof( x )
  return type(x)=='table' and metatable(x)
end

------------------------------------------------------------------------------
function copy( obj )
  local newobj = classof( obj ):new()
  for n,v in obj do newobj[n] = v end
  return newobj
end

------------------------------------------------------------------------------
------------------------------------------------------------------------------

B = class{
  name = 'Bob',
}

function B:who()
  print(self.name)
end

function B:hypotenuse()
  local x,y = self.x, self.y
  return sqrt( x*x + y*y )
end

a = B:new{
  x = 4,
  y = 99,
}

b = B:new{
  x = 5,
  y = 12,
}

print( b:hypotenuse() )

------------------------------------------------------------------------------
------------------------------------------------------------------------------
在這裡,類別「B」是物件「a」和「b」的方法的 meta-table 或類別表格。請注意「B.index」如何等於「B」。
$ lua std.lua -i
> foreach(B,print)
copy    function: 0x8066038
index   table: 0x8066a08
who     function: 0x8065d40
hypotenuse      function: 0x80660c0
name    Bob
new     function: 0x80659b8
> foreach(a,print)
y       99
x       4
> foreach(b,print)
y       12
x       5
> print( b:hypotenuse() )
13
> = B
table: 0x8066a08
>
您可以覆寫類別「B」的預設「new」方法,如下所示
>
> function B:new( x,y )
>> local obj = new( self, { x=x, y=y } )
>> return obj
>> end
>
> c = B:new(1,sqrt(3))
> foreach(c,print)
y       1.732050807568877
x       1
> = c:hypotenuse()
2
>
一個好用的技巧是用『偵錯』功能暫停並列印出函式內的變数値,之後只要輸入『cont』繼續就可。
> function B:something(x)
>> y = self
>> print(x,y)
>> debug()
>> print(x,y)
>> print"finish"
>> return x
>> end
>
> b:something(1234)
1234    table: 0x8066548
lua_debug> print(b)
table: 0x8066548
lua_debug> y=22
lua_debug> cont
1234    22
finish
>
最後一個注意事項。小心在 globals 表上使用標籤方法。由於 Lua 所有標準的函式都只是全域變數,除非您在區域變數中建立一個區域副本,否則當您試著呼叫其中任何函式時,標籤方法會造成無窮迴圈。這是為何您有時會看見類似以下的內容
local rawget, metatable, getglobal, gsub
    = rawget, metatable, getglobal, gsub

讓事件表同時兼具保存類成員的雙重作用是不是必要的呢?例如在 Python 類別中,特殊成員會以底線命名 (例如:__index__),因此不會與使用者欄位衝突。當 Lua 設計師決定在事件表規格中新增一些新欄位時,也存在相同的衝突問題。

不,這只是一個讓 Peter 使用的便利方式。您可以針對索引標籤方法使用不同的表;這表示只需要處理更多表。Peter 的程式碼有些古怪的地方 (例如定義 b:new(),但其用途並非百分之百明確),這是將索引表與事件表合併的決策所導致的結果。我會建議使用不同的表,即使會讓設計稍微有些複雜,除了這個原因之外,也是因為 John 反對名稱衝突。__foo__ 是一種醜陋的替代方式,不是解決方案:區隔命名空間才是解決方案。—— RiciLake

Roberto 最早建議使用元表來保存類別方法。我喜歡這種方式,因為這樣可以讓您這麼說

function some_class:some_method(some_params) does_something end

如果那是推薦的用法,那麼系統的事件表欄位一定會被命名為避免衝突。

以下內容應該可以解決 b:new() 問題,因為 obj.index 隨時提供類別表,無論 new 傳入的是類別表或類別的物件。

function class( t )
  t = t or {}
  t.index = t
  t.new = new
  t.copy = copy
  return t
end

function new( obj, init )
  init = init or {}
  return metatable( init, obj.index )
end

function copy( obj, init )
  local newobj = obj:new(init)
  for n,v in obj do newobj[n] = v end
  return newobj
end
$ lua -v std2.lua -i
Lua 4.1 (work4)  Copyright (C) 1994-2001 TeCGraf, PUC-Rio
> foreach(B,print)
copy    function: 0x8066fb0
index   table: 0x8067b40
who     function: 0x8066cb8
hypotenuse      function: 0x8067038
name    Bob
new     function: 0x8066b18
> foreach(b,print)
y       12
x       5
> w = b:new()
> foreach(w,print)
> =B
table: 0x8067b40
> =b.index
table: 0x8067b40
> =w.index
table: 0x8067b40
> z = b:new{ x=1,y=sqrt(3) } 
> = z:hypotenuse()
2
> foreach(z,print)
y       1.732050807568877
x       1
> zz = z:copy()
> foreach(zz,print)
y       1.732050807568877
x       1
> =z
table: 0x8068488
> =zz
table: 0x80686d0
> 
> b:who() 
Bob
> c=b:copy{ z=3 }
> foreach(c,print)
y       12
x       5
z       3
> c:who()
Bob
> 


RecentChanges · 偏好設定
編輯 · 歷史記錄
上次編輯:2006 年 12 月 31 日凌晨 12:13 (GMT) (版本差異)