Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Better class() function! (Has support for getters, setters, etc)

edited January 31 in Code Sharing Posts: 28

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.

Tagged:
Sign In or Register to comment.