类与方法

lua-users home
wiki

[!] 版本说明: 本教程使用 Lua 4.0 中的标签(tags)和标签方法(tag methods)概念,这些在 Lua 5.0 中已被元表(metatables)和元方法(metamethods)取代。请参阅 LuaClassesWithMetatable

类表

表可以作为关联数组使用。这种功能允许我们将函数存储在类表中,即函数名映射到函数。

A = {}

function A:add(x,y)
  return x+y
end

print( A:add(1,2) )  -- prints 3
在这个例子中,我们只能有一个类 `A` 的实例。如果我们想创建另一个实例,可以创建一个表(例如 `B={}`),然后将 `A` 中的所有方法复制到 `B` 中。

标签方法

可以通过使用 Lua 的标签方法来创建类的多个实例。标签方法可用于将函数请求从类实例重定向到类函数表。也就是说,我们不再需要将所有类函数复制到类的每个实例中。例如:

-- settag() returns a table "{}" which has been tagged with a new tag value
A = settag({},newtag())

-- use the index tag method to redirect a request for a function to the 
-- class function table
settagmethod(tag(A),"index", function(t,f) return %A[f] end)

function A:new(x,y)  -- create an instance of class A
  local t = {x=x,y=y}
  settag(t,tag(A))  -- tag the new table to tell it what its class type is
  return t
end

function A:add()
  print (self.x+self.y)
end

function A:sum(a)
  assert(tag(self)==tag(a)) -- check they are same class
  print (self.x+a.x,self.y+a.y)
end

a = A:new(7,9)  -- new instance of A
a:add()

b = A:new(2,4)  -- new instance of A
b:add()

a:sum(b)  -- "sum" of 2 instances
然而,示例中存在问题。
settagmethod(tag(A),"index", function(t,f) return rawget(%A,f) end)
a.number = 123
a:number()  -- try and call x
这将导致一个错误,因为我们试图调用一个数字。
a.sum = 7
print( a.sum )  -- should this print <function> or 7 ? (it prints 7)

解决名称冲突

这个问题无法解决,因为调用请求没有告知标签方法需要哪种类型。我们可以设置一个偏好,例如,类实例值优先于函数表值。(这样做可以支持函数重载。)

如果函数表优先,我们可以使用gettable标签方法,首先检查函数表 `A` 中是否存在该方法,例如:

settagmethod(tag(A), "gettable", 
          function(t,k)
            if rawget(%A,k) then
              return rawget(%A,k)
            else
              return rawget(t,k)
            end
          end )

统一方法:这个问题在 Edgar Toernig 的 Lua 分支 *Sol* 中得到了解决,通过引入统一方法。在这里,函数/方法表与一个标签相关联。当发生表查找时,取决于查找是如何调用的。也就是说,`a.foo` 和 `a:foo` 有不同的含义。`a.foo` 查找名为“foo”的成员,而 `a:foo` 在标记的函数表中查找名为“foo”的方法。因此,您可以拥有两个成员,一个是数据,一个是函数,它们可以和谐地共存,没有冲突。

为什么需要将方法和“数据”放在单独的命名空间中?Sol 的统一方法很酷,允许将方法和数据分开,并且比标签方法提供了一个更简单的系统。然而,我认为它的主要目的是允许实现类方法而不必让每个表都持有自己的副本。如果答案是某些类实例需要允许最终用户直接添加任意数据字段,我认为这可能不是一个好的设计,操作应该实现为独立的函数而不是类方法,或者字段访问应该通过受控接口进行。(我查阅了 PythonDictionaries。这个问题是不是试图模仿 Python 中的某个接口而不是提供一个适合 Lua 的接口的结果?)--JohnBelmonte

我不太确定您的替代实现相对于受控接口是什么。这能解决 `t.foo` / `t:foo` 问题(并允许存储任意数据)吗?它看起来像是现有的 Lua 标签方法系统,或者修改过的?我不认为这仅仅是试图模仿 Python 的结果,这里存在冲突。也就是说,您的实现受到限制。我以为元机制应该足够灵活以实现此类功能。这是一个实现风格的问题吗?使用独立函数可以避免这个问题,但我认为那样不太整洁(并且在这个例子中,不像 Python)--NDT

以 C++ 编写的容器为例。字段访问是受控的——要么通过 `operator[]`,要么通过专门的成员函数。字段访问和非字段访问之间没有歧义。Lua 的标签方法是有限的。您无法区分用于访问字段的 `table[x]` 和用于调用方法的 `table.x` / `table:x`。您 PythonDictionaries 问题的简单解决方案是放弃使用原生的表语法进行元素访问,并依赖于 `get` 和 `set` 等方法。这将使实现更简单!--JohnBelmonte

但这并不像 Python 的字典 :-) 我知道 Lua 不是 Python。Sol 会让我做我想做的事情,但 Lua 限制了我。我可以以多种方式调整代码以使其在没有冲突的情况下工作,但不是我想要的方式 :-( (也许应该添加一些变体...)。这就是我上面指出的,文本的倾向可能是支持统一方法 :-) 。在 Lua 中实现某些对象只能走这么远。我想这有点像 `table.n` 的问题——表是多用途的,但有局限性?--NDT

“在 Lua 中实现某些对象只能走这么远。”我不同意这一点。您遇到的问题纯粹是关于语法糖。Lua 不是一个花哨的宏语言。另一方面,它是一种强大的函数式语言。坚持使用函数式接口,事情会顺利解决。--JohnBelmonte

“在 Lua 中实现某些对象只能走这么远在语法上”会更准确。尽可能接近地模仿 Python 的字典,在功能和语法上,只是一个具有一些有趣结果的练习。Lua 在使用不同的语法/接口模仿功能方面没有问题。此页面的想法只是列出一些想法和限制。--NDT


另请参阅:示例代码PythonListsPythonDictionaries 包含类示例。
RecentChanges · preferences
编辑 · 历史
最后编辑于 2009 年 10 月 23 日下午 2:08 GMT (差异)