面向对象的 Lua 类泄漏问题

我已经阅读了很多关于面向对象的 Lua 编程(设置元表),并构建了一个带有继承的系统。

我的问题在于,某些变量似乎存在相互泄漏的情况。如果我调用一个名为 window:click(x, y) 的函数,这个函数会正常调用。这个函数的作用是通知所有组件有一个点击事件,这个函数已经做到了。

for number, component in pairs(self.components) do
    component.focus = false
    component:click(x, y, msg)
end

self.components 包含窗口中所有的组件。

作为所有组件的基类,我有一个叫做 component.lua 的类,这个文件创建了一个叫做 components 的表格,并向其添加了一个 create() 方法(做了所有常规的类似 Lua 的 OOP 操作)。这个基类包含了我想要在所有组件中使用的所有方法和变量,包括 component:click(x, y),并且这个方法再次被调用了。

for key, callback in pairs(self.clickCallback) do
    callback()
end
return

clickCallback 表格包含了在组件接收通知时应该调用的函数,并在 component.lua 中初始化。

从这里开始,我通过设置我的新组件(文本框、按钮、标签等)的元表来继承这个类。这些组件是添加到窗口中的 self.components 表格中的。

问题在于每个组件都应该有其自己的 clickCallback 表格。我通过 component.lua 中的一个 setter 来写它。

function component:addClickHandler(handler)
    table.insert(self.clickCallback, handler)
end

但是,当我在一个组件上调用 click(x,y) 时,它会调用所有的 clickHandlers,无论是为另一个按钮还是为一个标签。

正如您在上面看到的,我正在设置一个名为 focus 的参数,这似乎也遇到了相同的问题,其中为一个组件设置它(正如您在循环中看到的那样)会将其设置为所有组件(因此如果我有 4 个组件,则焦点在每个组件上都会重置 4 次)。

为什么 Lua 会这样做?有什么可以做来解决它?

点赞
用户501459
用户501459

首先,从你的小代码片段中尝试推断问题比直接发布完整的示例更加困难。

self.components 包含了窗口的所有组件

这可能是你的问题所在。再一次地,这只是猜测,因为你没有展示 create 方法,但是如果你的构造函数没有为每个实例初始化一个 clickCallback 成员,那么它将会使用类本身的表。

下面是一个说明问题的示例:

component = {}
component.__index = component

component.clickCallback = {}

function component.create()
  return setmetatable({}, component)
end

function component:addClickHandler(handler)
  table.insert(self.clickCallback, handler)
end

function component:click(x,y)
  for _,callback in pairs(self.clickCallback) do
      callback(x,y)
  end
end

a = component.create()
b = component.create()

a:addClickHandler(function(x,y) print("a", x, y) end)
b:addClickHandler(function(x,y) print("b", x, y) end)

a:click(10,20)
b:click(11,22)

下面是你描述的症状的输出:

a       10      20
b       10      20
a       11      22
b       11      22

换句话说,调用 a:click 会同时调用 ab 的处理程序,因为 clickCallback 表在类本身中,被所有该类的实例共享。修复方法是确保每个实例都有自己的处理程序表:

component = {}
component.__index = component

function component.create()
  return setmetatable({ clickCallback = {}}, component)
end

function component:addClickHandler(handler)
  table.insert(self.clickCallback, handler)
end

function component:click(x,y)
  for _,callback in pairs(self.clickCallback) do
      callback(x,y)
  end
end

a = component.create()
b = component.create()

a:addClickHandler(function(x,y) print("a", x, y) end)
b:addClickHandler(function(x,y) print("b", x, y) end)

a:click(10,20)
b:click(11,22)

输出:

a       10      20
b       11      22
2012-07-15 04:18:17