首页 > 解决方案 > 构建 Lua 类

问题描述

我正在 Lua 中构建一个类,其中包含许多相关函数组,但不确定是否有更好的方法来构建它。我目前必须为 Lua 5.1 环境进行开发,但我希望 Lua 5.3 在不久的将来成为可能。

该类将在许多不同的 Lua 程序中使用,所以我想要一些可以作为单个代码块放入的东西(我正在编程的环境意味着模块和 require 不是也不会是选项)。

理想情况下,我想要一段黑盒代码(公开的公共方法除外),而不是在不同的类中重复代码(以提高可维护性)。

我目前拥有的是(广义的):

 function Fclass()

     --here I declare a bunch of local functions that can be called by any of the public methods

     local function A(parms)
     end

     --Public methods set 1

     --here I declare a bunch of state variables shared by BSelector and GetB

     local BSelector = function()
         A(parmvalues)
         --returns a bunch of iup controls with supporting (complicated) logic 
     end

     local GetB = function()
         --returns the values of the iup controls created in Bselector
     end

     --Public methods set 2

     --here I declare a bunch of state variables shared by DSelector and GetD

     local DSelector = function()
         --returns a bunch of iup controls with supporting (complicated) logic 
     end

     local GetD = function()
         A(parmvalues)
         --returns the value of the iup controls created in Dselector
     end

     return{BSelector =BSelector , GetB =GetB,  DSelector =DSelector , GetD =GetD}
 end

“B”和“D”组方法是完全独立的,除了它们都使用局部函数“A”等(不依赖于外部变量);理想情况下,它们的状态变量应该是该组的本地变量。

这是一个合理的结构吗?或者我应该将“B”和“D”组分成两个单独的类,然后复制本地函数或将它们作为单独的代码放入?我真的不想在类之外公开本地函数,因为不可避免地会发生命名冲突......大多数程序将使用所有方法组,尽管有些程序只使用一个组。

还是有更好的方法来做到这一点?

我这样调用它们:

myB = Fclass()
myD = Fclass()
someresults = myB.Bselector()
otherresults = myD.Dselector()

更新添加:我被告知我可能没有正确使用术语,我正在做的不是课程。我的方法是基于Lua 中的编程并被选中是因为我想保留类的状态变量?目的?私有的——只能通过公共方法访问。

标签: classluascope

解决方案


在您的示例中,您似乎通过闭包而不是表值封装了实例的状态。

虽然这具有封装性更强的优点,因为在不使用调试库的情况下,upvalue 从外部是不可见的,但它也带来了 Lua 必须关闭每个实例的每个方法,浪费更多内存(虽然不是很多)的缺点。

另一个好处是当实例变量实现为表字段时,它们不需要在方法之前声明,因为表索引是基于字符串的,而当实现为闭包时,需要在定义函数之前知道局部变量(这也适用于其他方法,它们在任一实现中的工作方式与实例变量相同)。

更常见的做法是将实例变量存储为对象内的表值,并将对象作为第一个参数传递给函数。这甚至还有语法糖


在 Lua 中有很多方法可以做类,有很多不同的权衡(有些更擅长继承,而另一些表现更好,等等)

由于您似乎不需要任何继承,因此您可以使用简单的工厂函数,因为您已经在做很多事情了。

我个人喜欢构建此类工厂函数的方式是:

local object do
   local class = {}
   local meta = {__index=class} -- Object metatable

   function class:print() -- A method of the class
      print("Hello, I am an object and my x is " .. tostring(self.x))
   end

   function object(self) -- The factory function for the Class
      self.x = self.x or 0
      return setmetatable(self, meta)
   end
end

local o = object {
   x = 20
}
o:print()
o.x = 30
o:print()

这样做的好处是,对于具有许多方法和许多实例的类,方法不会复制到每个实例中,这样可以节省一些内存。

或者,你可以做这样的事情

local object do
   local function object_print(self)
      print("Hello, I am an object and my x is " .. tostring(self.x))
   end

   function object(self)
      self.x = self.x or 0
      self.print = object_print -- Store method directly in the object
      return self
   end
end

同样,这会在每个实例中保存对每个方法的引用,从而浪费一些内存。好处是您现在可以将类视为特征。当你写

person { name = "henry" }

您可以将其视为创建一个名为 Henry 的新人,但您也可以将其视为创建一个名为 Henry 的对象并向其添加人员特征。

由于将 OOP 的两个概念结合到一个实现中并且没有任何令人讨厌的继承的好处,在大多数简单的情况下,这是我最喜欢在 Lua 中构建对象的方式。


更新

trait 方法也适用于一起定义多个类/特征:

local person, human do
   -- Some generic method shared by both classes
   local function object_get_name(self)
      return self.name
   end
   -- Person uses this as a method, but human uses
   -- it as a function through an upvalue. Both work,
   -- but have different upsides and downsides.

   -- A method of person
   local function person_say_hi(self)
      print(self:get_name() .. " says hi!")
      -- Calling get_name as a method here
   end

   -- A method of human
   local function human_describe(self)
      print(object_get_name(self) .. ' is a human!')
      -- Calling get_name as an upvalue
   end

   function person(self)
      self.name = self.name or 'A person'
      self.say_hi = person_say_hi
      self.get_name = object_get_name
      -- Needs to be a method because person_say_hi assumes it to be one
      return self
   end

   function human(self)
      self.name = self.name or 'A human'
      self.describe = human_describe
      return self
   end
end

-- Create a new person
local henry = person{ name = "Henry" }
henry:say_hi()

-- Create a new human
local steve = human { name = "Steve" }
steve:describe()

-- Change the way henry gets his name
function henry:get_name()
   return self.name:upper()
end
-- This only affects henry; all other "person" objects keep their old 
henry:say_hi()
-- This only works because say_hi accesses the method

-- Add the person trait to steve
person(steve)
steve:describe() -- Steve is still a human
steve:say_hi() -- Steve is also a person now

推荐阅读