Hello! There are two OOP implementation methods I have seen in Lua. Which do you like more? #1:
local Car = {}
function Car.new(tireNum,color)
local Tires = tireNum
local Color = color
local isOn = false
local Methods = {}
Methods.turnOn = function()
isOn = true
end
Methods.getIsOn = function()
return isOn
end
Methods.getColor = function()
return Color
end
Methods.getTires = function()
return Tires
end
return Methods
end
Pros:
fields are private (do they ever need to be, though?)
intelligent code completion works since there is no metatable involved (ie. easier time working with the code down the road)
*potential to have private/public methods
Cons:
methods are duplicated each time an instance is made
not mainstream
more work writing accessor and mutator methods
#2:
local Car = {}
Car.__index = Car
function Car.new(tireNum,color)
local self = setmetatable({},Car)
self.Tires = tireNum
self.Color = color
self.isOn = false
return self
end
function Car:turnOn()
self.isOn = true
end
Pros:
mainstream
methods are not duplicated
Cons:
metatables = can’t use intelligent code completition
I prefer the second since I find it easier to find the methods I’m looking for. If you have a lot of methods for the object, the new/constructor function will get super long and convoluted. Being able to collapse individual methods, including the constructor, helps me find what I’m looking for quickly. Auto-complete also rarely works for me anyway, since it doesn’t work if you don’t reference the module directly, so that’s not much of a downside.
And by private, I mean that all methods and variables are private to each other? Get it? Heh. Jk.
Jokes aside I usually use #1 but when I’m making a large project that needs a lot of code to be written (my most recent one was like 40,000 lines of module scripts code) I tend to use #2 to prevent redundancy.
Honestly I wish I would have learned about meta tables earlier as they have been pretty useful for me.
I also think that the 2nd method feels a lot more like OOP in other languages.
You shouldn’t be using the 1st one at all. It incurs extra overhead every time the function keyword is hit because it has to make another closure to fit your needs.
Also, if you want private methods, do it the Python way and prepend them with __private__ or something. Yes Python does something like that internally.
So for a third option, I have adopted the RoStrap version of OOP. It has a bunch of neat features like actually private variables, built in maid/janitor class, type checking support and bunch of other really cool features, I highly recommend checking it out
I myself use a mutation of #2. I prefer to keep my methods inaccessible through the library. I do this by having the methods and constructor be in different containers. This is kinda like how you cant do CFrame:Lerp but instead you have to construct an actual CFrame datatype then call :Lerp on it.
What I mean by that is Car:turnOn would not be a valid call. Instead you would have to create a new Car instance and then call :turnOn; Car.new():turnOn()
Example:
If I am wanting to create a Car library that can create Car instances I would do something like this.
local car = {} --// table for the library
local carMethods = {} --// table for the methods
carMethods.__index = carMethods
function car.new(tireNum, color)
local newCar = {
tires = tireNum,
color = color,
isOn = false
}
setmetatable(newCar, carMethods)
return newCar
end
function car:turnOn()
self.isOn = true
end
Other
This didn’t seem to relevant so I thought I’d put it in a spoiler / hide details drop down thing.
I prefer to encase my methods in a do end statement so I can collapse code blocks to easily skim through my code.
-- [[ Car Class Declaration ]] --
local car = {}
do
--// Car Methods
local carMethods = {}
carMethods.__index = carMethods
function car.new(tireNum, color)
local newCar = {
tires = tireNum,
color = color,
isOn = false
}
setmetatable(newCar, carMethods)
return newCar
end
function car:turnOn()
self.isOn = true
end
end
As someone with a preference for functional programming I actually prefer the second one because the first one
uses closures as state, I prefer when state is enclosed in a single well explained place. In an ideal world functions should be stateless.
doesn’t work well with unit testing techniques such as replacing self with a dummy object.
The language Lua in particular however prefers the second one as well, it’s faster, more flexible, and (arguably) easier to read. While I absolutely understand the need for privacy (I wrote a whole library for Lua5.3 which adds privacy and strong typing in a much different way using interfaces), the easiest way to accomplish privacy is to simply lie in your documentation. If someone happens to stumble upon your private variables and happens to mutate them like a lunatic then either they know what they’re doing or it’s their own fault
Coming from the perspective of someone who is not very familiar with OOP, the second method looks a lot cleaner. The downside of not being able to use intelligent code completion seems pretty minor compared to it’s benefits.