What about if you change
Class.__index = function(self, Index)
return self.BackingTable[Index]
end
to
Class.__index = function(self, Index)
return self.BackingTable[Index] and self.BackingTable[Index] or Class
end
What about if you change
Class.__index = function(self, Index)
return self.BackingTable[Index]
end
to
Class.__index = function(self, Index)
return self.BackingTable[Index] and self.BackingTable[Index] or Class
end
Still nil.
Did a test and removed Class.__index = blah from the .new function and calling either PropaneTank:Destroy() or self:Destroy() would work, so its something going on in with that __index. …Is it overwriting the Tool metatable’s method? or something?
@magnalite - I’ve gone ahead and fixed the formatting of your post to work with Discourse I kept the same formatting and content, just made it work with Discourse’s formatting which doesn’t support [center] BBCode. I’ve also moved it to the Tutorials section. Thank you for this wonderful tutorial!
@Lilly_S Thanks! Much appreciated
The way .__index works is it is called when an index does not exist. In this case when you create an object you create a new empty table with data about the objects in. It has none of the methods in it. To allow your object to have the appropriate methods called on it a metatable with its .__index pointing to the class table is assigned to the table with the objects data in it. When .__index returns a table lua looks into that table to see if the index exists in that too. So what actually happens when you call a method on an object is this.
Object:DoSomething()
Object[“DoSomething”] is nil, does its metatable have an .__index set?
It does, .__index returned SomeClass.
Does SomeClass[“DoSomething”] exist?
It does! call it!
This allows you to assign the methods without duplicating them into every single object you make.
In your case you are making .__index return a value from BackingTable and so it never has a chance to see if the method exists in the Class table.
I’m not sure why my previous suggestion wouldn’t work though
Probably because there was no Class[Index], just Class.
And by doing;
Class.__index = function(self, Index)
return self.BackingTable[Index] or Class[Index]
end
I was able to get the :Destroy() function to work. ^.^ Thanks!
@Lilly_S Thanks for updating and moving the thread!
^-^ Btw I find it helps if I keep my classes in separate module scripts and require them when I need them.
Thanks for this tutorial! I am trying to learn OOP and I have a quick question for anyone who can answer it.
I have an object that has events connected to it (and some values like Power, Agility, etc.)
Is this all that is necessary for a :Delete() function?
function Thing:Delete()
for i,v in pairs(self) do
if tostring(v) == "Connection" then
v:disconnect()
end
end
self = nil
end
Or do I also have to iterate through all the values and set them to nil, like self.Power = nil
, or is just setting self = nil
fine?
This is incredibly helpful! I’ve always been vaguely aware of OOP and have used objects once or twice, but never really understood how it worked or how to properly utilize it. This cleared up a lot of questions!
No, that is sufficient. It will GC (garbage collect) the members when all references are lost to them (and if all references are lost to the object, you’re good to go!)
I like to call setmetatable(self, nil
) on my code too. It’s a good defensive programming practice, as it means dead objects don’t have methods to call.
You’ll notice the snippet you used is going to be a pain to type out. My solution is to use Maid. In my deconstructors I’ll just deconstruct Maid which cleans up my signals. Here’s an example:
function CameraControls:Disable()
if self.Enabled then
self.Enabled = false
self.Maid:DoCleaning()
self.Maid = nil
self.LastMousePosition = nil
UserInputService.MouseBehavior = "Default"
end
end
-snip-
i have found a solution to my question
Would you recommend having multiple Module Scripts for different things?
So there’s gonna be a lot of functions for weapons in my game; same for AI. Would it be worth having two Module scripts. One called Weapon and one called AI? Or should I just use one?
Only advantage I can think of for using 2 is organisation; but I think that’s important ( to me atleast! ).
You should split the code up as much as reasonably possible. Each class (module) should also try to keep to itself as much as possible (ie not directly editing objects from other classes and be as self containing as possible).
This idea is known as “encapsulation” which is one of the “three pillars of oop”.
For those looking for an easy way to implement replication triggered by setting object properties, here is a sample of a simple way to do this:
Car = {}
function Car.new(position, driver, model)
local newcar = {}
newcar.Speed = 0
local mt = {}
function mt.__index(self, index)
return Car[index] or newcar[index]
end
function mt.__newindex(self, index, value)
self:Replicate(index)
newcar[index] = value
end
local proxy = {}
setmetatable(proxy, mt)
return proxy
end
function Car:Replicate(property)
--RemoteEvent:FireAllClients()
end
function Car:Boost()
self.Speed = self.Speed + 5
end
I know nothing about efficiency, so I couldn’t tell you if this is fast or not, but it seems to do the job.
The key difference with this vs. the method outlined in the OP is that custom metamethods are defined for each object. These metamethods are called whenever someone reads or writes a value in the proxy
table. The table is nominally empty because any reads are caught by __index
and sent to Car
or newcar
and any writes are sent to newcar
. The __index
method is technically backwards from lua’s implementation – by default if the key trying to be access in the table is nil
it falls back to the table. It means you can’t overwrite anything defined in Car
in the instance, but it seems like a questionable practice to do this regardless.
Know this is old, but looking for some advice.
So I have my own object, called machine. Things can transfer resources into a machine. So it makes sense to create a method
machine:Transfer(resource)
However there are many types of machine, and so machine:Transfer() has different functionality depending on the machine type. Similar situations arise with other methods for the machine. What is the best way to handle this?
Currently I have a table of machine types, that looks something like this
table[Id] = {Name = “Blablabla”, TransferFunction = AnotherTable.Machine1TransferFunction}
So the machine object looks like this
function machine:Transfer(resource)
table[self.Id].TransferFunction(self,resource)
end
But this certainly feels wrong.
You should be able to just override Transfer in your extended class, no?
Sorry, but I have no idea what that means. I think I’ve still got a lot of OOP to learn!
assuming machine
is the superclass, you should have an extended class (sub-class) for each type of machine you have, that all inherit from the superclass… and in those sub-classes, you should be able to override the behavior or Transfer
.
As in, if your original class is Machine, and you extend it with CoolMachine, and you want to change the behavior of :Transfer, you should be able to just…
function CoolMachine:Transfer(resource)
--code
end
And it’ll override the default Machine:Transfer() behavior.
Ah, I get it all now, thanks!
…jeeze how did I go so long without knowing this stuff?
As you asked about speed, and as a general warning, this method of replication is not particularly efficient. Things which are convenient come at the price of performance (usually).
In this case you are giving every object a new meta table which isn’t so bad, just uses a bit more memory which we have plenty of nowadays.
However the real problem is now every single access to an object is routed through a proxy meta table as well as a function call (or with it linked to a replication function then even more!).
Practically I am not sure what real world performance impact this would have. However i am sure that if you use oop a lot in a project, you will notice this slow you down.
While this seems like a good idea (and to be honest it is quite a nice tidy idea), it should only be used as a temporary solution.
(Also note that replication like this should attempt to buffer changes. While ROBLOX does do this behind the scenes it has a fair bit of overhead so doing your own buffering is advised. Particularly you should attempt to create “delta frames” which are logs of only the changes made and multiple changes to a variable get condensed into whatever the final value is before the delta frame is sent across the network.)