A few questions about OOP / Metatables

OOP (Object Orientated Programming) / Metatables has always been an issue for me. To keep it simple, I just don’t understand it at all.

Many have told me to look at documentations either from Roblox or from lua.org however I simply unable to understand and I don’t really have anyone to actively ask my questions so I’ve decided to just pile up the questions in this one post. I have many questions regarding why I should use OOP and under what circumstances it should be used.

  1. Under what circumstances should this be used?
  2. Why should I use it?
  3. I could achieve most of the functionalities in OOP using normal tables, are metatables better then tables?
  4. Are meta tables save able in datastore? (One time when I was saving a inventory system I was told to learn meta tables.)
  5. Do I have to clean up the metatables when I don’t need them like setting it to nil?
  6. How does self work?
  7. How would I be able to re-reference the table inside the table?
local table1 = {
properties = {
a = 1
}
Function = function()
print(self.properties.a) -- I'm trying to print 1 inside the table
end

}

I’ve searched the forums to see if there were any tutorials but I wasn’t able to find any. I feel like I’ve just hit a roadblock and unable to continue.

1 Like

If you can’t think of a use-case for OOP, you likely don’t need to use it. OOP is heavily overrated and only really should be used where having classes makes sense in your code-base.

A metatable is simply special instructions for how to do a certain thing, nothing special.

As far as I’m aware, no.

No. Metatables are just instructions that are attached to a table.

e.g.

local array = {"a", "b", "c"}
setmetatable(array, {
    __tostring = function() -- the tostring metamethod is useful for debugging
        return "<array>"
   end
})
print(array) -- this outputs '<array>' as print uses tostring under the hood

Within a : method (See below), you have access to the self global, it’s a direct reference to the object the function is under.

local methods = {}

function methods.dot()
-- as this is using the 'dot', we do not have access to self
-- although we could use the 'methods' variable directly as a
-- substitute.
end

function methods:colon()
print(self == methods) --> true
end

-- these two are functionally identical
methods:colon()
methods.colon(methods)

methods:dot()
-- you should avoid using colons for methods that
-- are not a part of objects. not 100% sure what'd happen here.

-- as per convention, constructors and non-class-methods should
-- use the dot, whereas, actual class methods (that would need self)
-- should use a : 

Your code example is a bit unclear. From my understanding, you want to do this:

local object = {}
object.properties = { a = 1 }

-- if using 'classes', this would be very useful
-- when using singletons, not so much.
function object:printA()
    print(self.properties.a)
end

Using OOP in practise

Here’s an example of a class:

Counter.lua

local Counter = {}
-- 'index' means that Roblox will fallback to the 'Counter'
-- table if something is indexed that is nil
Counter.__index = Counter

function Counter.new()
	-- as we're not using a :, 'self' is just a regular variable
	-- no globals are being overriten here!
	local self = {
		number = 0
	}
	
	-- You could just do {__index = Counter} here iirc
	-- not sure why you index the actual object, oh well
	setmetatable(self, Counter)
	
	-- We are returning an object with a metatable!
	return self
end

-- As this has a :, we can use 'self'
function Counter:count()
	self.number = self.number + 1
	return self.number
end

return Counter

And then, we can use our new class like so:

-- like any module, we must require it.
local Counter = require(path_to_counter)

-- we can create multiple versions of our counter!
-- each of these will have their own independent variables
local counterA = Counter.new()
local counterB = Counter.new()
local counterC = Counter.new()

-- for loop to speed stuff up lol
-- not the most efficent code
for i = 1, 100 do
	if i > 50 then
		counterA:count()
	end
	
	if i < 25 then
		counterB:count()
	end
	
	counterC:count()
end

print(counterA.number) --> 50
print(counterB.number) --> 24
print(counterC.number) --> 100

Although all of these are on the same module, we have multiple different revisions with little overhead on our part.

Further Reading

I just covered the basics of OOP. But there is so much more! including sub classes (whaaaa?)

I personally suggest this tutorial: (although you can use the search tool to find many more topics about OOP!)

8 Likes