Easiest way to do OOP (Object Oriented Programming)

Side-note: Majority of the functions in your tutorial have a syntax error:

The parenthesis after end are not supposed to be there because they don’t close anything

1 Like

I understand your method is more efficient, but I’d say for most basic games like roleplaying and fps games it should suffice. My method’s advantage is that it’s really easy to set up and is beginner-friendly. But your method is definitely better for large and complex projects, and mine is probably not the best in that case.

Thanks for your tutorial it is good.

1 Like

Agreeable. Just make sure to add a disclaimer about high memory on large projects.

2 Likes

It’s a way to pack an unknown amount of arguments into one parameter. You can get all of them in a table by doing local values = {...}

Example:

local function printArgs(...)
    local words = {...}

    for _, word in ipairs(words) do
        print(word)
    end
end


printArgs('one', 'two', 'three')
2 Likes

Isn’t this teaching bad habits? If you get used to doing it in an inefficient way it could be hard to re-adjust when you move on to larger projects.

It’s still better than not using any OOP method at all. Even though it’s slower than the other OOP method, it’s not noticeable for most use cases.

I still don’t understand the idea of using an inferior method simply because its slightly easier to understand. Creating a function inside an object wastes memory, you shouldn’t just throw it under the rug because “it’s not noticeable for most use cases”. Imagine hundreds of objects being created all with their own function that is identical to each others, it would definitely be noticeable.

1 Like

I updated the post with a disclaimer about my method not being the fastest.

I’m just wondering, is anyone able to make my OOP method as optimized as @ffrostfall 's method while keeping it as simple as my method? I’d really appreciate it!!

Instead of doing this:

local Person = {}

function Person(name, age)
    local self = {}
    
    self.name = name
    self.age = age
    self.iq = 5

    self.IncreaseIQ = function(amount)
        self.iq += amount
    end)

    return self
end

return Person

Just do this:

local Person = {}
Person.__index = Person

function Person.new(name, age)
    local self = setmetatable({}, Person)
    
    self.name = name
    self.age = age
    self.iq = 5

    return self
end

function Person:IncreaseIQ(amount)
    self.iq += amount
end

return Person
4 Likes

That seems like a pretty good method. Can you explain what exactly you are doing on the second line? Thanks. Person.__index = Person

Person is a metatable of self, when an index isn’t found in self, the metamethod __index is invoked, if this is a function, it will be called with the arguments self and key, if it’s a table, it will be searched through instead for the index (a key)

local meta = {}
meta.__index = meta
local object = setmetatable({}, meta)
-- or
local meta = {}
local object = setmetatable({}, {__index = meta})
local meta = {}
local object = setmetatable({}, meta)
meta.__index = function(self, key)
    print(self, "WAS INDEXED WITH THE KEY", key)
    return math.random() -- will returns some pseudo random number
end

print(object.some_key)

A metatable is simply a table that “describes the behavior” of another table.

Useful resources:
https://www.lua.org/pil/13.html
https://developer.roblox.com/en-us/articles/Metatables
https://devforum.roblox.com/t/an-in-depth-look-at-oop-roblox-lua/1049827

I wrote multiple articles on OOP, not just this one. I suggest going through them in order, or you will be confused. I also advice not to use the OOP paradigm for everything, just the core game systems that logically should use objects. In the case of everything else, a modular design patter is more efficient.

I agree; OOP tends to be overused, sometimes a simple function will suffice or a different paradigm such as a data oriented design paradigm would be better.

1 Like

This isn’t the case, Luau has introduced an optimization on which it ‘caches’ functions if they are the same, a function can look the same and not be the same, so it’s important to understand how that works.

If you take his implementation and get two objects and compare their functions together, it will, infact, return true.

Your ‘test code’ somewhat breaks this optimization, my guess is that it’s because the functions are declared inside a table construct, and not after a table is created.

For example, in this case, it does return true.

local function Class()
	local self = {
		_number = 0
	}
	
	function self:Add(num)
		self._number += num
	end
	
	return self
end

print(
	Class().Add == Class().Add
)

Something to note is that using this method does make the creating of the object a lot slower, and it does use some more memory, which is the memory needed to be allocated for the extra entries for these functions in the table, however, there are some benefits, and one of those is type satefy. It just works better with types, metatable OOP breaks with metatables from what I’ve experienced, and this method did not.

The tutorial isn’t that great, like, the example is ehh, using table.index = function() is really ugly. Also, they could’ve declared the properties (not the methods) on the table construct it self, etc, but this method in its principle is fine. AFAIK, there’s not really big performance advantages to metatable OOP, at least since well, 2021. (I have not been active basically since then) Most optimizations have to do with indexing, and I believe it’s actually faster for Luau to find the function index this way, not sure.

I actually have a community tutorial on this exact method and I’ll be tagging it here, just need to find it.

1 Like

Is there another way to create a function instead of doing table.index = function() as you mentioned on your last paragraph? Thanks!

I’m aware, I just didn’t go into that because that wasn’t the point of the post. Functionally, that’s what the __index metamethod does, and that’s how oop works.

It was test code to show how it worked, it wasn’t meant to be used at all. Just to show how it worked.

Yeah! That would be:

function table.index(...)
    return ...
end

It definitely didn’t sound like it, the entire post was directed at the fact those functions would be separate instances which isn’t the case;

I’m aware and it didn’t work as test code, it didn’t demonstrate how that would actually behave when someone were to use this method properly.

2 Likes

Oh yeah and another thing, the code shown by the OP actually does not work with function caching because the reference to self is the one created by the function so when the function is created, it’s actually different each time because it references a variable that only exists in the context of the object function being created.


4 Likes