Как всем известно, в lua нет как таковых классов и объектов. Однако есть метатаблицы и синтаксический сахар.
С помощью указанных механизмов достаточно просто реализовать подобие классов.
В итоге и получается нечто такое:
local MyClass = {} -- the table representing the class, which will double as the metatable for the instances
MyClass.__index = MyClass -- failed table lookups on the instances should fallback to the class table, to get methods
-- syntax equivalent to "MyClass.new = function..."
function MyClass.new(init)
local self = setmetatable({}, MyClass)
self.value = init
return self
end
function MyClass.set_value(self, newval)
self.value = newval
end
function MyClass.get_value(self)
return self.value
end
local i = MyClass.new(5)
-- tbl:name(arg) is a shortcut for tbl.name(tbl, arg), except tbl is evaluated only once
print(i:get_value()) --> 5
i:set_value(6)
print(i:get_value()) --> 6
Всё это конечно хорошо, даже при определённой сноровке можно реализовать наследование…
Но где public и private члены класса? Дефакто в этом примере они все public. Да ещё и надо помнить, где использовать двоеточие:
MyClass:myFunc()
а где просто одну точку:
MyClass.myOtherFunc()
А статические члены класса? Неужели придётся отказываться?
Вот я и не захотел отказываться, и начал колхозить…
Итак, представляю вам мой колхоз:
createClass = function()
local creator = {}
creator.__private = {
object_class = {},
}
creator.__oncall = function(class_creator)
-- Get the class definition so we can make needed variables private, static, etc.
local this = class_creator()
-- Initialize class from class definition
__init = function()
-- Init Public Static
local class = {}
if (type(this.__public_static) == "table") then
class = this.__public_static
end
-- Init Object
local thisClass = this
local __constructor = function(...)
local object = {}
local this = class_creator()
-- Init Public
if (type(this.__public) == "table") then
object = this.__public
end
-- Init static values of the class
this.__public_static = thisClass.__public_static
this.__private_static = thisClass.__private_static
-- Call Constructor
if (type(this.__construct) == "function") then
this.__construct(...)
end
-- Returning constructed object
return object
end
return {class = class, constructor = __constructor}
end
-- Creating class (returning constructor)
local class_data = __init()
local class = class_data.class
local constructor = class_data.constructor
-- Set class metatable (with setting constructor)
local class_metatable = {
__newindex = function(t, key, value)
if type(t[key])=="nil" or type(t[key])=="function" then
error("Attempt to redefine class")
end
rawset(t, key, value)
end,
__metatable = false,
__call = function(t, ...)
if type(t) == nil then
error("Class object create failed!")
end
local obj = constructor(...)
creator.__private.object_class[obj] = t
local object_metatable = {
__newindex = function(t, key, value)
class = creator.__private.object_class[t]
if type(class[key])~="nil" and type(class[key])~="function" then
rawset(class, key, value)
return
end
if type(t[key])~="nil" and type(t[key])~="function" then
rawset(t, key, value)
return
end
error("Attempt to redefine object")
end,
__index = t,
__metatable = false,
}
setmetatable(obj, object_metatable)
return obj
end,
}
-- Setting class metatable to the class itself
setmetatable(class, class_metatable)
-- Returning resulting class
return class
end
return creator.__oncall
end
createClass = createClass()
А пользоваться как? Очень просто, вот вам шаблон:
myclass_prototype = function()
local this = {}
this.__public_static = {
-- Public Static Variables
statvalue = 5,
-- Public Static Funcs
statfunc = function()
print(this.__public_static.statvalue)
end,
}
this.__private_static = {
-- Private Static Variables
privstatdat = 2,
-- Private Static Funcs
privstatfunc = function()
print(this.__private_static.privstatdat)
end,
}
this.__public = {
-- Public Variables
pubdata = 3,
-- Public Funcs
pubfunc = function(newprivate)
print(this.__public.pubdata)
this.__private.privdata = newprivate
end,
}
this.__private = {
-- Private Variables
privdata = 1,
-- Private Funcs
listallprivate = function()
print(this.__private.privdata)
end,
}
this.__construct = function()
-- Constructor
end
return this
end
myclass=createClass(myclass_prototype)
Как видите, при каждом вызове изнутри класса придётся каждый раз указывать путь, а ля «this.__private.privdata», зато вот вам пример использования созданного класса!
myobject = myclass()
myobject.pubfunc(999)
При вызове этого кода будет создан объект myobject из класса myclass, и будет вызвана функция pubfunc, которая высветит содержимое публичной переменной и изменит приватную.
И никаких заморочек с двоеточиями!
Кстати, статические вызовы тоже работают. Как из класса, так и из объекта.
Итак, вкратце расскажу, что за магия здесь происходит. А происходит тут жонглирование так называемыми upvalues. upvalues — это переменные, которые изнутри видны, а снаружи — нет! Очень похоже на private, не так ли?
Так вот, создав функцию-«прототип», мы создали новую область видимости, и в неё поместили все внутренности нашего класса, вынеся наружу только public и public static члены класса. А всю остальную магию выполняют метатаблицы, которые позволяют определить, что именно будет происходить при запросе «несуществующего» члена внешней таблицы, которая представляет наш класс/объект.
Сумбурно звучит, знаю, но лучше не могу объяснить — не спец :)
Долго думал, как можно сделать наследование при такой системе, но так и не придумал — upvalues достаточно серьёзно ограничивает наши действия, а извращенствами вроде debug библиотеки пользоваться не хотелось — она не везде включена. Если кто додумается, буду рад увидеть!
PS: если для кого-то мой пост нечто очевидное — чтож, значит я себя переоценил :) Не судите строго, может кому-то зачем-то пригодится это решение!
Автор: Evengard