0
obj = {}

function obj:setName(name)
    print("obj: ", self)
    print("name: ", obj)
end

我创建了一个对象并分配了一个类似上面的方法。现在我这样称呼它:

obj:setName("blabla")

然后自我标识符指代 obj。我的问题是该功能也可能通过以下方式访问

obj.setName("blabla")

在这种情况下,obj 不会作为参数传递,“blabla”将代替 self 参数而不是提供名称。这是因为函数声明中的 : 运算符只是

function obj.setName(self, name)

我可以以某种方式正确检查 self 是否真的是主题/该函数是否已由冒号运行?不能从 argCount 中得知,也不能直接在函数中编写 obj,因为它将被实例化,并且该函数是从我定义它的范围之外引用的。我唯一的想法是检查自己是否拥有成员“setName”

function obj:setName(name)
    if ((type(self) ~= "table") or (self.setName == nil)) then
        print("no subject passed")

        return
    end

    print("obj: ", self)
    print("name: ", obj)
end

但这也不干净。

编辑:现在这样做:

local function checkMethodCaller()
    local caller = debug.getinfo(2)

    local selfVar, self = debug.getlocal(2, 1)

    assert(self[caller.name] == caller.func, [[try to call function ]]..caller.name..[[ with invalid subject, check for correct operator (use : instead of .)]])
end

function obj:setName(name)
    checkMethodCaller()

    print(self, name)
end
4

2 回答 2

0

您可以为对象分配一个元表,并在 setName 方法中检查 self 的元表是否合适:

obj = {}
local objmt = {}

setmetatable(obj, objmt)

function obj:setName(name)
    if getmetatable(self) ~= objmt then
        error("Panic, wrong self!")  -- or handle it more gracefully if you wish
    end
    self.name = name
end

编辑:

当然,如果有人故意替换或删除您的元表,它将完全破坏该功能。

于 2013-05-19T20:18:38.150 回答
0

通常,文档在脚本中胜过类型检查。如果您检查所有内容,您最终会看到一些性能影响。首先添加好的函数文档标题。

也就是说,我想到了以下选项:

通常只对 self-argument 进行类型测试就足以防止输入错误,因为name通常是字符串,所以如果你不小心输入obj.setName("Foo")了类型self是字符串而不是表格。

-- Test type of argument
function obj:setName(name)
   assert(type(self) == "table");
   -- more code
end

您当然也可以使用参数的数量。请注意,我使用>= 2而不是== 2,这很有用,因为如果您链接某些调用(例如obj:setName(foo:getInfo())附加返回值),则即使您的name参数可能是正确的值,也会中断执行。

-- Check number of arguments
function obj.setName(...)
   assert(select('#', ...) >= 2);

   local self, name = ...;
   -- more code
end

以下变体更进一步,它不仅确保您self是一个表并包含正确的函数,而且self同一个表。这是因为比较表不会比较它们在 Lua 中的内容,而是比较它们的唯一表 ID。

但是,这也需要对每个实例化的方法和对象都有一个闭包。这有点开销。

-- Compare against object table
function Construct()
   local o = {};

   function o:setName(name)
      assert(self == o);
      -- more code
   end

   return o;
end

想到的最后一个变体(主要是因为我编写了一些与它非常相似的代码)是在使用原型时通过构造函数跟踪您的对象

local Objects = setmetatable({}, { __mode = "k" });
-- the __mode just makes the GC ignore this table so that your objects can be
-- collected by the GC if not used any more. It would work without the metatable
-- but be a memory leak.
local Prototype = {
   setName = function(self, name)
      assert(IsMyObject(self));
      -- more code
   end
}

function IsMyObject(self)
   return not not Objects[self];
end

function ConstructMyObject()
   local o = {};
   Objects[o] = { __index = Prototype };
   return setmetatable(o, Objects[o]);
end

明显的好处是您的方法不再是每个对象的单独闭包。但是,通过这种方式,您还可以轻松地执行其他操作,例如使对象不可变或实现基本继承。

于 2013-05-21T14:52:21.343 回答