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)

Мога ли по някакъв начин правилно да проверя дали self наистина е обектът/дали функцията е била изпълнена от двоеточие? Не може да се каже от argCount, нито мога да напиша obj във функцията директно, защото тя ще бъде създадена и функцията се препраща извън обхвата, където я дефинирам. Единствената ми идея е да проверя дали self притежава член "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
Наистина ли сте сигурни, че искате да направите това? Този вид магия може да бъде объркваща и податлива на грешки в дългосрочен план. Може би можете да промените дефиницията на вашия :-using обект, за да използвате . методи вместо това или може би можете просто да добавите флаг, за да кажете на вашата функция за обработка дали трябва да използва : или . методите?   -  person hugomg    schedule 19.05.2013
comment
@missingno Това е кратък период. Написах малка библиотека, която ще изпълнява специална цел за други потребители. Те просто не трябва да могат случайно да получат достъп до функционалността по фалшив начин/това се случва и на мен, като избера грешен оператор. Нотацията на метода е по-добра за четливост. Допълнителен флаг би бил още по-склонен към грешки и има функции с променливи параметри.   -  person user2399203    schedule 20.05.2013
comment
Те просто не трябва да могат случайно да получат достъп до функционалността по фалшив начин: Но как ще можете да сте сигурни, че евристичният метод за откриване на обект работи през цялото време? Наистина ли сте сигурни, че не можете да промените интерфейса си, така че да има само един начин да правите нещата?   -  person hugomg    schedule 20.05.2013


Отговори (2)


Можете да присвоите метатаблица на обекта и вътре в метода 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

РЕДАКТИРАНЕ:

Разбира се, ако някой умишлено замени или премахне вашата метатаблица, това напълно ще наруши функцията.

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