Hello everyone, I am very new to learning OOP and I have seen people use something called a “Constructor”, where they do stuff such as Car.new(). However, while it does make some sense on how it kind of works, I do not understand how this would work in practice. How would anything “new” be created that is not just printable text in the output. If, for example, I wanted to clone a part using OOP and to change its properties, how would I use the .new() constructor and methods to change its properties?
Ok so first off to make OOP easier to organize, I am going to explain a few things. So if you have a table a and b. doing ,setmetatable(a,{__index = b})
. This basically makes it so if you index table a and get a nil value, it checks table b to see if that index exists. Also a general idea of module script is needed.
So for this to be a little easier to explain, I am going to actually add a few methods and properties for this block. For properties lets say
-
Color
-
Material
Methods:
- Change Color
So I am going to set up the basic module script
local BlockClass = {}
BlockClass.__index = BlockClass
function BlockClass.new(blockInformation)
local newBlock = {}
setmetatable(newBlock,BlockClass) -- this allows you to call methods easier
newBlock.Part = Instance.new("Part")
newBlock.Part.BrickColor = blockInformation.BrickColor or BrickColor.new("Pastel Blue")
newBlock.Part.Material = blockInformation.Material or Enum.Material.Plastic
newBlock.Part.Parent = game.Workspace
return newBlock
end
return BlockClass
This is the new constructor, it essentially makes you create a new object in this BlockClass
so if you call it from another script, it returns a table with all the properties of this Object. Now the reason for using setmetable
is to allow you to easily call methods.
Lets make a new method that changes the block color
local BlockClass = {}
BlockClass.__index = BlockClass
function BlockClass.new(blockInformation)
local newBlock = {}
setmetatable(newBlock,BlockClass) -- this allows you to call methods easier
newBlock.Part = Instance.new("Part")
newBlock.Part.BrickColor = blockInformation.BrickColor or BrickColor.new("Pastel Blue")
newBlock.Part.Material = blockInformation.Material or Enum.Material.Plastic
newBlock.Part.Parent = game.Workspace
return newBlock
end
-- by using BlockClass.ChangeColor(self) is the same thing as BlockClass:ChangeColor()
function BlockClass:ChangeColor()
self.Part.BrickColor = BrickColor.random()
end
return BlockClass
Now inside the script that is actually creating this object
local BlockClass = require(script.Parent.ModuleScript)
local block = BlockClass.new({BrickColor = BrickColor.new("White"),Material = Enum.Material.Neon})
block:ChangeColor()
When you do block:ChangeColor()
it is the same thing as block.ChangeColor(block)
This is when the metatable becomes useful. When block indexs ChangeColor
, it sees a nil value, then it checks the metatable index and sees it is indexing BlockClass
It checks BlockClass
table and sees that there is a function called ChangeColor()
and calls that function.
That’s the basics
Oh and Clarification on the metatables, when I make a new table in the .new()
operator and then do setmetatable(newTable,BlockClass)
So when you return this new table and you check a value in it, it now checks the BlockClass since the BlockClass has a __index = BlockClass
. This is useful for inheritence.
There’s really no point to over complicate this. Just use Instance.new("Part")
.
Roblox’s DataModel is, itself, an OOP system. You don’t need to reinvent the wheel on this one.
I do know that Instance.new exist, but I just wanted to understand how OOP can be used in-game.
Using Instance.new
is using OOP, is what I’m saying.
The fact that you have to ask how to use “Lua OOP” shows that you’re trying to use OOP for the sake of using OOP, instead of having an actual reason to do so. If it doesn’t innately make sense to you, you’re going to constantly run into roadblocks and headaches that you could avoid by writing code you’re more comfortable with.
That is exactly what I am trying to do, I want to use OOP just for the sake of OOP to try to learn how to use it. Of course Instance.new() is a much better solution. I just want to learn how it works and how to create constructors and methods for future use.
Oh one thing to add on about OOP, its more viable in the long run, I did my old code in a way that I felt comfortable. It works until you want to add more stuff to it.
This is subjective to your use case though. Using OOP for things that necessarily do not need to be object orientated isn’t very efficient.
Agree with Semistare, and disagree with both on principle: OOP has a few very nice and useful features held back by an, in my opinion, insane design philosophy.
Like I said before, if OOP doesn’t innately make sense, it’s not going to be useful to you. And I would go so far as to argue that OOP cannot make sense when you start to try actually implementing it at scale.
There’s a lot of minor and major nuance to this discussion, but I highly recommend giving Brian Will’s video series critiquing it a watch:
Links
Object-Oriented Programming is Bad - YouTube
Object-Oriented Programming is Embarrassing: 4 Short Examples - YouTube
Object-Oriented Programming is Garbage: 3800 SLOC example - YouTube
Object-Oriented Programming is Good* - YouTube
All things considered, OOP is not inherently evil, but trying to use it for everything is just going to hurt you in the long run. Structured code need not be object oriented.