lua: проверить тему метода

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)

Могу ли я как-то правильно проверить, действительно ли я является субъектом/если функция была запущена двоеточием? Это нельзя узнать из 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

person user2399203    schedule 19.05.2013    source источник
comment
Вы можете написать это в одну строку: assert(self.setName) чтобы остановить выполнение вашего скрипта при неправильном вызове.   -  person Egor Skriptunoff    schedule 19.05.2013
comment
Есть идея, сравните self.setName с obj.setName (функция). Если это совпадает, это должна быть вызываемая функция.   -  person user2399203    schedule 19.05.2013
comment
Вы действительно уверены, что хотите это сделать? Такая магия может сбить с толку и привести к ошибкам в долгосрочной перспективе. Возможно, вы можете изменить определение вашего :-использующего объекта, чтобы использовать методы . вместо этого, или, может быть, вы могли бы просто добавить флаг, чтобы указать вашей функции обработки, следует ли ей использовать методы : или .?   -  person hugomg    schedule 19.05.2013
comment
@missingno Это короткий пробег. Я написал небольшую библиотеку, которая предназначена для других пользователей. Они просто не должны иметь возможность случайного доступа к функциональности ложным образом / это случается и со мной, когда я выбираю неправильного оператора. Обозначение метода лучше для удобочитаемости. Дополнительный флаг был бы еще более подвержен ошибкам, и есть функции с переменными параметрами.   -  person user2399203    schedule 20.05.2013
comment
Они просто не должны иметь возможности случайного доступа к функциональности ложным образом: но как вы сможете быть уверены, что эвристика обнаружения объектов работает все время? Вы действительно уверены, что не можете изменить свой интерфейс так, чтобы был только один способ делать что-то?   -  person hugomg    schedule 20.05.2013


Ответы (2)


Вы можете назначить метатаблицу объекту и внутри метода setName просто проверить, подходит ли метатаблица себя:

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

РЕДАКТИРОВАТЬ:

Конечно, если кто-то намеренно заменит или удалит метатаблицу, это полностью нарушит функцию.

person W.B.    schedule 19.05.2013

Обычно документация превосходит проверку типов в сценариях. Если вы проверите все, вы в конечном итоге увидите некоторое влияние на производительность. Начните с добавления хороших заголовков документации по функциям.

При этом на ум приходят следующие варианты:

Обычно для защиты от ошибок при наборе достаточно просто проверить тип собственного аргумента, поскольку 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 сравнивается не их содержимое, а уникальный идентификатор таблицы.

Однако это также требует закрытия для каждого созданного метода и объекта. Это немного накладные расходы.

-- 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

Очевидным преимуществом является то, что ваши методы больше не являются индивидуальным замыканием для каждого объекта. Однако таким образом вы также можете легко делать другие вещи, например, делать ваш объект неизменяемым или реализовывать базовое наследование.

person dualed    schedule 21.05.2013