So I only recently picked up Object-Oriented Programming and I realized that prototype-based classes were used a lot to create objects in a lot of examples.
-- Regular OOP implementation
Car = {}
function Car.new(position, driver, model)
local newcar = {}
newcar.Position = position
newcar.Driver = driver
newcar.Model = model
newcar.Speed = 0
Boost = function(tbl, speed)
tbl.Speed = tbl.Speed + 5
end}
return newcar
end
-- Prototype-Based Class implementation
Car = {}
Car.__index = Car
function Car.new(position, driver, model)
local newcar = {}
newcar.Position = position
newcar.Driver = driver
newcar.Model = model
newcar.Speed = 0
setmetatable(newcar, Car)
return newcar
end
function Car:Boost()
self.Speed = self.Speed + 5
end
Isnât Prototype-Based Classes just a better and more optimized way of dealing with objects? Or is there an underlying difference between the two implementations that I am unaware of?
Both implementations are good, but have differences that can be crucial depending on what you want to achieve.
The first way
Is simpler, less noisy.
Is more efficient, as the message resolution mechanism is more straightforward.
It spends more memory and instantiating a class is slower. This is because for each instance it has a complete copy of all its methods.
Its encapsulation mechanism is stronger and simpler than its counterpart.
The second way
It is more complicated as it requires metatables for its implementation, which in turn makes it noisier. There are some tools that alleviate this somewhat.
Message resolution is slower than its counterpart, since it depends on the internal mechanism of metatables. Although this is not really a problem since Luau has quite a few optimizations in this regard.
It spends less memory and instances are created faster. This is because all methods can be reused.
Its encapsulation mechanism is very weak.
These are just some differences (there are more). The first way seems to have more advantages, but everyone prefers to use the second way, mainly because of memory:
In games it is typical to create many instances dynamically, so the faster they are created the better.
In general, memory is much more expensive than CPU, so everyone prefers to save memory. And Lua (and certainly Luau) is one of the fastest languages because it prioritizes CPU usage.
The mechanism for resolving menages in the second way is similar to the OOP implementation that other languages implement. If you come from other OOP languages it feels a bit familiar.
__index has to be used as a middleman to the table that has methods, so every time you call a function youâre technically calling two. the first block calls the function directly.
Message resolution refers to how an object will respond to certain messages. I will explain it a little.
A class defines the actions (i.e. methods) that its instances will perform in response to each message sent to those instances.
Now, in roblox we can say that we pass messages as follows:
[SomeObject]:[Message]
For example:
workspace:GetChildren() -- 'GetChildren()' is the mesage passed to the workspace object.
game:GetService("Players") -- 'GetService("Players")' is the message passed to the game object.
In the case of workspace, the action (method) GetChildren is defined in the Instance class.
Now, how does an object know what action to perform on a given message? The answer is that the message and the action have the same identifier (name). So all they have to do is run a search algorithm to find the corresponding action to each message. This search algorithm is what is called message resolution. Basically it is a loop that goes through all the methods and compares their names to the message name until it finds a match.
So we can say that it is slower because it additionally requires running a search algorithm which in the case of Lua is that metatable mechanism.
It is stronger because you can use the function scope to create true private members. By creating a local variable, which would represent a field, or a local function, which would be equivalent to a method, no one external to the object would be able to access them because they are local to the function.
While in its counterpart it is not possible to do that. There are also no reserved words as in java (Private, Protected). What there is is a naming convention: if the name of a member starts with an underscore, then it is private. But this will not prevent someone external to the object from using it.