So earlier, I was working on an improved version of the class() function. I managed to get better class extension, inline class definitons (I’ll explain later in the post), getters, and setters working!
Here’s the code:
function class(...)
-- Inititalize locals
local __args__ = {...}
local __class__ = {}
local __metatable__ = {__isClass = true, __getters = {}, __setters = {}}
local __parents__ = {}
-- When class variable is called
__metatable__.__call = function(__class__, ...)
-- Initialize locals
local args = {...}
local inst = {}
local metatable = {__classvals = {}}
-- Transfer the class values into a metatable (in case the class has metamethods)
for k, v in pairs(__class__) do
metatable[k] = v
end
-- Handles the class' setters
metatable.__newindex = function(inst, key, value)
local classmetatable = getmetatable(__class__)
if classmetatable.__setters[key] ~= nil then
local settermetatable = getmetatable(classmetatable.__setters[key])
metatable.__classvals[key] = settermetatable.__func(inst, value)
else
metatable.__classvals[key] = value
end
end
-- Handles the class' getters
metatable.__index = function(inst, key)
local classmetatable = getmetatable(__class__)
if classmetatable.__getters[key] ~= nil then
local gettermetatable = getmetatable(classmetatable.__getters[key])
return gettermetatable.__func(inst)
else
return metatable.__classvals[key]
end
end
-- In case the class definition has metamethods
setmetatable(inst, metatable)
-- Loop through parent classes. We loop backwards so that earlier arguments take priority
for i = #__parents__, 1, -1 do
-- Set things like methods
for k, v in pairs(__parents__[i]) do
inst[k] = v
end
-- Initialize with parent constructors
if type(__parents__[i].init) == "function" then
__parents__[i].init(inst, ...)
end
end
-- Call the class constructor, it's last so it takes highest priority
for k, v in pairs(__class__) do
inst[k] = v
end
-- Sync functions and variables from class to instance
if type(__class__.init) == "function" then
__class__.init(inst, ...)
end
-- Give back our class instance
return inst
end
-- When we initialize a getter or setter, we need to keep track of it. That's done here.
__metatable__.__newindex = function(__class__, key, value)
if type(value) == "table" then
if getmetatable(value).__getter == true or getmetatable(value).__setter == true then
local metatable = getmetatable(__class__)
if getmetatable(value).__getter == true then
metatable.__getters[key] = value
else
metatable.__setters[key] = value
end
return
end
end
rawset(__class__, key, value)
end
-- Set the metatable for the class
setmetatable(__class__, __metatable__)
-- Look for parent classes, and add them as parents if necessary
for k, v in pairs(__args__) do
if getmetatable(v) ~= nil and getmetatable(v).__isClass == true then
table.insert(__parents__, v)
else
-- Is an argument not a parent class? That means its the class definition!
__class__ = v
end
end
-- Return the class table
return __class__
end
-- Getter function to create a getter
function getter(func)
return setmetatable({}, {__getter = true, __func = func})
end
-- Setter function to create a setter
function setter(func)
return setmetatable({}, {__setter = true, __func = func})
end
To use this improved class function, you’ll need to:
a. Copy & paste the code above into a new tab in your project,
OR b. Make a new project, paste this into a new tab in it, and add it as a dependency to any projects you want to use this in.
How to use this:
The class() function itself has 2 possible argument sets:
class(parentA, parentB, …) - Use this if you don’t want to have an inline class definition. The parent classes are the classes to extend your class from. (Note: when extending multiple classes, the first class passed in takes priority, then the second, then the third, etc… This is for everything that gets transferred from class to class.)
OR class(inlineDefinition, parentA, parentB, …) - Use this if you want to use the inline definition. The inline defintion parameter should be a table, and all of the variables in the table will be copied over to the class.
Here’a an example of the inline definition:
MyClass = class({
init = function(self, a, b)
self.a = a
self.b = b
end,
otherFunc = function(self)
print(self.a, self.b)
end,
c = 10
})
Note that the first parameter of the functions is self!
Getters and setters are pretty easy to use. When you’re setting a property of the class definition, set the value you want to be the getter/setter to a function, which is passed into getter() or setter(). The function passed into getter() will take 1 parameter, which is the class instance. You must return a value, which will be sent when you use the getter. The function passed into setter() takes 2 parameters, the class instance and the value to set. You don’t need to return a value here.
Here’s another example:
Test = class()
function Test:init(a)
self.a = a
end
Test.b = getter(function(inst)
return inst.a + 1
end)
Test.c = setter(function(inst, value)
inst.a = value * 2
end)
test = Test(3)
print(test.a) -- 3
print(test.b) -- 4
test.c = 5
print(test.a) -- 10
That’s all I have for now. I hope you’ll find this useful! Also, if you have any questions, feel free to reply, as I can help.