Are Prototype-Based Classes a better way to use OOP?

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.

(Examples below are from: All about Object Oriented Programming)

-- 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?

2 Likes

with the second way all cars use the same function, which saves space

2 Likes

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.

1 Like

Thank you for this detailed response! Just some questions though:

  1. What do you mean by ‘Message Resolution’, and why is it considered slower in the second way?
  2. Can you explain why the encapsulation mechanism is stronger in the first way than the second?

__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.

I hope this made sense.

2 Likes

Thank you for your detailed response! I understand the concept a lot more now. Also thanks @Speedmask for your contribution as well, it helped a lot! :slight_smile: