Am I implementing classes correctly?

This is how I implement classes, I haven’t ever used OOP in Luau and I’m waiting for the day I actually use OOP.

local Account = {
	balance = 0
}
Account.metatable = { -- all metamethods go here
	__index = Account
}

function Account.new(balance)
	local self = setmetatable({}, Account.metatable)
	self.balance = balance
	return self
end

function Account:Withdraw(amount) 
	self.balance = self.balance + amount -- I was testing in repl.it relax
end

function Account:Deposit(amount)
	self.balance = self.balance - amount
end

local a1 = Account.new()
local a2 = Account.new(100)
print(a1.balance) --> outputs 0
print(a2.balance) --> outputs 100

I don’t understand why I have to re-define self, that’s the thing that confuses me the most.

3 Likes

If you’re unclear about redefining self in the constructor, you actually aren’t. Self is a variable which points back to the table a method was called from if you call the method with a colon. If not, then self is nil. It’s just that in Roblox OOP, developers tend to use self as the variable representing the object in the constructor - it can be anything you want there.

The metatable implementation you have isn’t necessarily incorrect, but it’s not the typical way that it’s done for Roblox OOP. __index is added as a property of the class table instead. You can do it the way you’re currently doing it but you’d have to make quite a few changes. I’ll get back to that.

If you want to do it the “standard” way, it’s as follows:

local Account = {balance = 0}
Account.__index = Account

function Account.new(balance)
    local self = setmetatable({}, Account)
    if balance then
        self.balance = balance
    end
    return self
end

The rest of your methods are fine as is.

Now if you wanted to do it the way you’re currently doing, you’d have to do it a bit differently. I typically prefer to do it the “prototyping” way primarily so that the constructor and a few class-level variables aren’t present in the object, but it’s probably not important in most cases since it’s implied you won’t use the constructor off of an object.

Roughly, your code would look like this:

local Account = {}
Account.metatable = {balance = 0}

function Account.new(balance)
    local self = setmetatable({}, {__index = Account.metatable})
    if balance then
        self.balance = balance
    end
    return self
end

function Account.metatable:Withdraw(amount)
    -- code
end

function Account.metatable:Deposit(amount)
    -- code
end

local a1 = Account.new()
local a2 = Account.new(100)
print(a1.balance)
print(a2.balance)

At this point though, probably better to make the metatable its own table rather than to also include it in the Account class table, since for every method we’re defining we’re also doing a table lookup for the metatable index before defining the method’s index as a function.

More on self and OOP-in-Lua here:
https://www.lua.org/pil/16.html

6 Likes

Idk really Looks like it but double check on Lua.org like @colbert2677 said

Thanks for the reply, it was pretty informative.

local Account = {
	balance = 0
}
Account.__index = Account

function Account.new(balance)
	local self = setmetatable({}, Account)
	self.balance = balance

	return self
end

function Account:Deposit(amount)
	self.balance = self.balance + amount
end

function Account:Withdraw(amount)
	self.balance = self.balance - amount
end

local a1 = Account.new(100)
local a2 = Account.new()

print(a1.balance) --> 100
print(a2.balance) --> 0

I used the Account table as a table and metatable, is this okay? I really like the syntax.

Yep, that works - in fact, that’s typically the standard when working with classes. The balance part in the Account class table wouldn’t be necessary though since that just gets overrided anyway in your constructor.

1 Like