【问题标题】:lua class - can't set property and retrieve valuelua 类 - 无法设置属性和检索值
【发布时间】:2013-12-31 16:47:42
【问题描述】:

我正在尝试在 lua 中创建类。我编写了一些简单的代码来设置和获取字符串值,但结果我一直返回 null。

这是我的类定义的样子:

appUser = {}
appUser_mt = { __index = appUser }

-- This function creates a new instance of appUser
function appUser:new()
    local new_inst = {}    -- the new instance
    setmetatable( new_inst, appUser_mt)-- all instances share the same metatable.
    return new_inst
end
-- Here are some functions (methods) for appUser:
function appUser:setLastName(lname)
    appUser._lname = lname
end

function appUser:setFirstName(fname)
    appUser._fname = fname
end

function appUser:getLastName()
    return appUser._lname
end

function appUser:getFirstName()
    return appUser._fname
end

return appUser -- do I need this???

我尝试创建 2 个用户的测试代码如下所示:

require "user"
local a = appUser:new{setLastName="Doe"}
local b = appUser:new{setLastName="Doe-Doe"}
print(a.getLastName())
print(b.getLastName())

当我从命令行运行测试脚本时,这是我得到的:

mydevbox:/usr/share/vsx/lib/testapp# lua testuser.lua
nil
nil
mydevbox:/usr/share/vsx/lib/testapp# 

到目前为止我所做的尝试:

  1. 我尝试改变调用方法的方式 打印(a.getLastName()) 到 打印(a:getLastName())

  2. 我已将变量定义更改为:

    appUser._lname 到 _lname

让我的代码看起来像这样:

local _lname
function appUser:setLastName(lname)
    _lname = lname
end

function appUser:getLastName()
    return _lname
end

我不确定我做错了什么。任何建议,将不胜感激。 谢谢。

编辑 1

对代码进行了以下更改以测试 Etan 的答案和我的答案: 1.我已将构造函数更改为接受我的答案中所述的参数... 2. 我在 set/get 函数的主体中添加了对“self”的引用。

-- This function creates a new instance of appUser
appUser = {}
appUser_mt = { __index = appUser }

function appUser:new(new_inst)
    new_inst = new_inst or {}    -- the new instance
    setmetatable( new_inst, appUser_mt)-- all instances share the same metatable.
    return new_inst
end
-- Here are some functions (methods) for appUser:
function appUser:setLastName(lname)
    self._lname = lname
end

function appUser:setFirstName(fname)
    self._fname = fname
end

function appUser:getLastName()
    return self._lname
end

function appUser:getFirstName()
    return self._fname
end

测试脚本中的对象 A 和 B 仍然以 nil 失败,但 C 有效,因为我创建它的方式与实例化 a 或 b 不同。 (请看我的回答)

编辑 2

appUser = {}
appUser_mt = { __index = appUser }

function appUser:new()
    return setmetatable( {}, appUser_mt)-- all instances share the same metatable.   
end

local function validate_vmail(vmail_id)
    local success = true    
    if type(vmail_id) ~= 'string' then
        success = false
    end

    if not exists_in_database(vmail_id) then
        success = false

    end
    return success
end 

function appUser_mt.__index(t, k)
  if k == 'lastName' then
    return t._lastName
  end
  return rawget(t, k)
end


function appUser_mt.__newindex(t, k, v)
    local success = false
    if k == 'lastName' then
        k = '_lastName'
        v = v:gsub('^.', string.upper) -- Make sure the first letter is uppercase.
        success = true
    end
    if k == 'vmail_id' then     
        if validate_vmail(v) then
            v = v
            success = true
        else
            success = false
        end
    end
    if success then
        rawset(t, k, v)
    end 
end    
return appUser

这是实例化这个类的客户端:

local a = appUser:new()
a.lastName = "doe"
print(a.lastName)

local b = appUser:new()
b.lastName = "dylan"
print(b.lastName)

a.vmail_id = 1234
print(a.vmail_id)

代码似乎可以正常工作,但我想确保到目前为止我理解了您的 cmets / 答案。

【问题讨论】:

  • a 和 b 仍然不起作用,因为您需要使用 appUser:new{_lname="Doe"} 而不是 appUser:new{setLastName="Doe"}。您需要预定义您的方法分配的字段,而不是使用您的方法名称预定义字段。
  • Etan,顺便说一句,感谢您提供的所有帮助。我理解您关于设置字段而不是方法的评论。但从技术上讲,在进行 OOP 时,客户不应该知道我的类中使用的内部变量的名称......这就是为什么我想提供公共方法来访问和更改值的原因。这有意义吗?
  • 是的,我明白你为什么要这么做。我的意思是你不能按照你写的东西去做。如果您愿意,您可以在新函数表参数中从方法名称转换为字段名称(或更好的方法调用),但这不是您编写的代码。

标签: class oop object lua


【解决方案1】:

模块

您没有在任何地方分配require "user" 的结果,所以我假设您可能在某处隐藏了module 调用。如果您打算在不调用module 的情况下使用它,则需要return appUser,例如:

-- mlib.lua
local M = {}
function M.add(a, b) return a + b end
return M

-- script.lua
local m = require'mlib'
print(m.add(2, 2))

构造函数

你可以简化你的构造函数。

function appUser.new()
  return setmetatable({}, appUser_mt)
end

我想我理解您对以下代码的意图,但如果不进行一些调整,它就不会按照您希望的方式工作。我会回到这个。

local a = appUser:new{setLastName="Doe"}
local b = appUser:new{setLastName="Doe-Doe"}

功能

请记住,任何时候使用: 定义函数时,都会插入一个隐式self 参数作为第一个参数。调用函数也是如此。

function t:f() end
function t.f(self) end
t.f = function(self) end

所有这些行都是等效的语句。

t['f'](t)
t.f(t)
t:f()

设置器/获取器

Setter 和 getter 并不像其他语言中需要的那样随处可见。 Lua 中的表没有私有或公共接口的概念,因此如果您所做的只是分配和检索,那么您当前的设置将来可能会成为维护责任。执行以下操作没有任何问题。

local class = {}
class.lastName = 'Doe'

事实上,有 metamethod 工具,如果您真的想要 setter 但也希望它们融入 与其他属性。举个例子吧。

local mt = {}
function mt.__index(t, k)
  if k == 'lastName' then
    return t._lastName
  end
end

function mt.__newindex(t, k, v)
  if k == 'lastName' then
    k = '_lastName'
    v = v:gsub('^.', string.upper) -- Make sure the first letter is uppercase.
  end

  rawset(t, k, v)
end

local class = setmetatable({}, mt)
class.lastName = 'smith'
print(class.lastName) -- Smith

构造函数,pt。 2

为了稍微分解一下,让我们看看下面的语句。

local a = appUser:new{setLastName="Doe"}

{setLastName = "Doe"} 部分只是用名为setLastName 的键(或属性)构造一个表,其值为"Doe",它们都是字符串。然后将其用作appUsernew 函数的second 参数。 appUser 是第一个参数,用作前面提到的隐式self 参数。记住:

这实际上根本没有必要。调用appUser.new 并将其定义为没有self 的函数就足够了,因为它不需要。这就是全部。 new 还没有任何特殊之处,它会以某种方式知道使用参数 setLastName 调用 "Doe",但我们可以这样做。


说了这么多,这里有一个关于如何让构造函数按照你想要的方式工作的想法。

function appUser.new(options)
  return setmetatable(options or {}, appUser_mt)
end

local a = appUser.new{_lname = 'Doe'}
local b = appUser.new{_lname = 'Doe-Doe'}

但是,如果您真的想在构造函数表本身中调用函数,则需要更多。

function appUser.new(options)
  local o = setmetatable({}, appUser_mt)

  for k, v in pairs(options)
    -- This will iterate through all the keys in options, using them as
    -- function names to call on the class with the values
    -- as the second argument.
    o[k](o, v)
  end

  return o
end

local a = appUser.new{setLastName = 'Doe'}
local b = appUser.new{setLastName = 'Doe-Doe'}

如果这还没有尽可能完整地回答您的问题,请告诉我。

【讨论】:

  • 瑞恩,首先,感谢您提供的详细信息!我很感激你的时间。在您的 Setters/Getters 部分下的示例中,我有以下问题。 1) __newindex() 方法中的 k='_lastName' 赋值行有什么作用? 2)如果我正确理解了您的 cmets / 示例,那么我希望我的用户类具有的所有属性都应该在 __index() 和 __newindex() 方法中定义。它是否正确?类方法呢?像“deleteuser()”方法还是“addwidget()”方法?
  • 1) k = '_lastName'rawset 重定向到_lastName,而不是在lastName 上设置值。这是必需的,因为 __newindex__index 仅对不存在的键进行操作。因此,例如,如果foo.bar 已经设置为3,那么foo 的元表__newindex 将永远不会对bar 属性的任何进一步分配/检索作出反应。 2) 只有您需要影响任何更改的属性。在 vanilla Lua 中,这种将接口与实现分离的想法并不强烈。事实上,您的所有函数都与变量共享相同的空间。
  • 请参阅我的编辑 2,了解我认为应该如何设置/获取值和验证值的示例
  • +1 表示 lua 并不真正支持接口与实现的分离。我似乎很难在 lua 中做 OOP...也许这就是为什么...
  • 我很抱歉。 __index 中的 rawget 完全没有必要。除此之外,就 OOP 而言,您所做的似乎对 Lua 来说有点夸大其词。老实说,它感觉更像 C++。你读过Programming in Lua吗? 1116 章节可能有助于您了解 Lua 中如何使用表。用户 wiki 上还有 OOP page。最后,我会说最简单的解决方案可能是最好的。
【解决方案2】:

我实例化对象的方式失败了……

require "user"
local a = appUser:new{setLastName="Doe"}
local b = appUser:new{setLastName="Doe-Doe"}
print(a:getLastName())
print(b:getLastName())


local c = appUser:new()
c:setLastName("Dylan")
print(c:getLastName())

对象“c”的结果正确显示...

所以我想问题出在我的构造函数上。我试过把它改成:

function appUser:new(new_inst)
    new_inst = new_inst or {}    -- the new instance
    setmetatable( new_inst, appUser_mt)-- all instances share the same metatable.
    return new_inst
end

但这还没有固定对象 a 或 b,但我得到了 c 的正确结果

【讨论】:

  • 您 c 对象“有效”,因为您正在调用 setLastName 函数。但是,在创建 a、b 和 c 对象之后,就像您尝试调用 print(a:getLastName())print(b.getLastName()) 一样,看看您得到了什么,您会看到我使用 self 与 appUser“类”本身得到了什么(或作用域局部)在函数体中。
  • Etan,如果我理解您的最后评论,您是在要求我尝试调用 getLastName() 方法……我正在为对象“c”做这件事。它正在工作。
  • 是的。我建议您在同一个文件中创建所有三个对象后,在对象“a”或“b”上尝试该操作,然后查看所有对象的结果。如上创建a,如上创建b,如上创建c,如上为c调用getLastName,为a调用getLastName,为b调用getLastName。
【解决方案3】:

修改后的代码看起来应该可以正常工作,但是它与原始代码一样,存在一个问题,即它只支持所有 appuser 实例的单个 lname 或 fname。您需要在定义的函数中使用 automagic self 变量,以便它们正确地对传入的实例进行操作。

function appUser:setLastName(lname)
    self._lname = lname
end

function appUser:getLastName()
    return self._lname
end

此外,这段代码并没有按照您的想法执行(以及您希望它执行的操作)。

local a = appUser:new{setLastName="Doe"}
local b = appUser:new{setLastName="Doe-Doe"}

它没有在您的新 appUser 实例上调用 setLastName 函数。您正在创建一个表({...}),为该表提供一个具有给定值 DoeDoe-DoesetLastName 键,然后将该表传递给 appUser:new 函数(然后迅速将其忽略为它不需要任何参数)。

【讨论】:

  • 我 *相信" 我不需要明确引用 self,因为我在函数定义中使用了 ":" 符号。我可能是错的......我只是在学习.但是请看我刚刚发布的答案。
  • 用于创建函数的: 表示法意味着您不需要在函数参数中使用self。这就是automagic 部分。当你想引用“调用”对象时,你仍然需要在函数体中使用 self。
  • Etan,你能解释一下为什么我的“c”对象目前可以工作,而函数体中没有对 self 的引用吗?谢谢。顺便说一句,您可以查看我帖子中的编辑 1。我已经尝试过您将“self”添加到正文中的建议,但它不起作用。
猜你喜欢
  • 2017-06-01
  • 2019-10-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-15
  • 2017-07-17
  • 2012-12-07
相关资源
最近更新 更多