All about Object Oriented Programming

Personally I prefer this version:

function Car.new(position, driver, model)

	local self = setmetatable({}, Car)
	
	self.Position = position
	self.Driver = driver
	self.Model = model

	return self
end

This is the least ambiguous version IMO albeit admittedly it does look redudant compared to yours.

2 Likes

Alternate way of formatting

----------------------------------------------------
-- Define Class Attributes
----------------------------------------------------
Car = {
	Position = nil;
	Driver = nil;
	Model = nil;
	Speed = nil
}

----------------------------------------------------
-- Define Class Methods
----------------------------------------------------
function Car:Boost()
	self.Speed = self.Speed + 5
end

----------------------------------------------------
-- Define Class Constructor
----------------------------------------------------
function Car.new(tbl)
	local newcar = tbl or {}
	setmetatable(newcar, Car)
	Car.__index = Car
	return newcar
end
2 Likes

If I have this:

local PlayerModule = {}
PlayerModule.__index = PlayerModule

-- Constructer function to create a new character  
function PlayerModule.new(character)
	local newCharacter = {}
	setmetatable(newCharacter, PlayerModule)
	
	newCharacter.Name = character.Name 
	newCharacter.Stamina = 100 
	newCharacter.BonusHealth = 0 
	table.insert(characters, newCharacter)

	return newCharacter	
end

return PlayerModule 

How would I get the newCharacter that I created without doing .new() every time. I thought of making a table and inserting the newCharacter into that table to get it that way. Am I missing something about OOP here?

So my solution is now this:

local PlayerModule = {}
PlayerModule.__index = PlayerModule

local characters = {}

-- Constructer function to create a new character  
function PlayerModule.new(character)
	local newCharacter = {}
	setmetatable(newCharacter, PlayerModule)
	
	newCharacter.Name = character.Name 
	newCharacter.Stamina = 100 
	newCharacter.BonusHealth = 0 
	table.insert(characters, newCharacter)

	return newCharacter	
end

-- get the character 
function PlayerModule:GetCharacter(name)
	for i, v in pairs(characters) do 
		if v.Name == name then 
			return v 
		end
	end
end

return PlayerModule 

I’m not sure if this is the correct way to do it.

The character you make is returned from the function call.

local playerModule = require("PlayerModule")
local myChar = playerModule.new(character)

After that, you use myChar to refer to the made character from then on, using .new again will make a separate character; resulting in the resetting of values.

Your solution would allow you to create a player in one script and then find a reference to it from a separate script. This is quite useful if you have things broken apart into multiple scripts;

As far as keeping track of a data structure after you make it, poking it into a table or variable is pretty much your only option. The question is where, if the creation is triggered in the same script as all other uses, you can just keep it with the rest of your code.
Your solution has it in the module to be reference along with creation which works well with keeping everything player related together.
Alternatively, you could create a singleton module script to hold object references.

1 Like

self is equal to Car. We can rewrite that code as follows:
function Car.Boost()
Car.Speed = Car.Speed + 5
end

self is just syntax sugar. But remember that you have to use a colon if you want to use self. Otherwise - if you use a dot and not a colon - self will be equal to nil.

3 Likes

Because the question was discussed elsewhere follow below for any people from the future.

Are there any good practice methods to make a :CleanUp() method on an object, and have it work with both this class and inherited classes?

Wow! 9 years old and still excellent.

Is there some way I can get type hinting from Roblox-LSP using the example?

--module script called Car in game.ReplicatedStorage
Car = {}
Car.__index = Car

function Car.new(position, driver, model)
    local newcar = {}
    setmetatable(newcar, Car)

    newcar.Position = position
    newcar.Driver = driver
    newcar.Model = model

    return newcar
end

function Car:Boost()
    self.Speed = self.Speed + 5
end

return Car

--main script

Car = require(game.ReplicatedStorage.Car)

carLot: {Car} = {}
-- as before

The last line, carLot {Car} = {}, gives my Roblox-LSP extension cause to complain:

Undefined type Car.Roblox LSP Diagnostics.(undefined-type)

Thanks

You have to make sure you define the type.

type carlot = {typeof(Car.new())} -- Note: A car object is not constructed during type definition
-- It's easier than manually doing:
--     ^usually
-- type carlot = {
--   [number]:{
--             Position:Vector3,
--             Driver:Player?,
--             Model:Model,
--            };
-- }
-- Note: 'typeof' really only properly evaluates value types if you define the types somewhere along the line (ie: parameters and or return value(s), etc..)
local Carlot:carlot = {}
2 Likes

You’re a life-saver, thanks. Using this extensively on much better code now.

You’re welcome.

Declaring types is very important for aiding readability and maintainability in a similar way commenting parts of your code does.

i’ve always considered myself intermediate but after going down this rabbithole i realized how much of a beginner i am,but i managed to grasp how these custom ‘classes’ can be used:

lets say we want to create candy,instead of having to go through every detail everytime we want to create a new one,we could create a candy class,meaning by just using 'CandyMaker.new(flavor,size) we could create a piece of candy

,now lets say we want to create a lollipop for example,now lollipop is basically candy except it has more properties,it has a taste,and size,but it also has special properties such as the length of the lollipop stick for example,so we could add those functions into the ‘LollipopMaker’ module, that, by my opinion, saves A LOT of time

Can’t believe this tutorial is almost a decade old and still holds up this well. Excellent job!

1 Like

I discovered this thread a while back but come back to it now and again to remind myself/correct myself on what I’m doing but this thread has been incredibly helpful in metatables and I’ve even implemented it into my projects. Great resource :slight_smile:

Very nice tutorial!

I’ve actually written a module that I might open-source later that actually makes implementing OOP very easy including inheritance.

Calling module:class(your_table_here) automatically assigns it a :new() function for making instances and a :init() function that gets called every time you make a new object for setting initial values.

All these functions can be overwritten if desired.
Calling module:class(table1, table2) for instance will turn table1 into a class that inherits from table2.

Using bindable events, other scripts can communicate with classes without having to require() them or know of their existence, should all be relatively memory safe too.

Your tutorial is nice and I hope more people will utilize OOP, it’s unfortunate Lua doesn’t just have classes like C++, C# and Java.

Using metatables still feels a bit hacky an unofficial.
They’re also slower and less efficient than native OOP implementations.
Unfortunately tables use more memory than objects. :frowning:

Hello there, I’m new to OOP so I have a question. Does Car.new() actually create a new car ? If so then how do you define what Class it is?

Great tutorial, very clear and concise, however I would go over metatables for new people more in depth, as it’s a essential aspect of replicating oop within luau

it’s just a function that puts its arguments inside of the self value.

love the idea! sounds simple to make though.

I had trouble seeing the point in OOP, then I read this and it all makes sense lol.