Ok so I have been reading around a bit and suddenly I found out that Lua is an Object Oriented Programming language. Could someone explain to me what that means? Also what does it look like? Have I been using it all this time without knowing? Lastly, if I am not using it currently, should I be? If anyone could help I would be extremely grateful.
Had to split this answer into two parts because it was too long
Lua is an imperative programming language, not an object-oriented language, though you are able to implement OOP in Lua yourself.
OOP is a programming paradigm, meaning a style in which you program and a way in which you're meant to think about your code. With imperative programming, you are specifying commands for the computer to execute so you achieve your desired result with your program.
First, it's important to define what an abstraction is. An abstraction is a level of conceptual removal from something. For example, the Roblox API is an abstraction over a bunch of C++ code. Rather than interfacing with C++ code directly, the Roblox API makes this simple and intuitive. Abstractions are valuable because they let you concentrate on the actual problem being solved in a simpler and more elegant manner. With good abstractions, you generally don't need to understand exactly how the abstraction was implemented.
The fundamental notion of OOP is the object. An object is just like it is in the real world. Objects can have data associated with them (called attributes) or behaviors (called methods).
Let's take a zombie as an example of an object
Here's some attributes the zombie might have: - Their name - The shirt they are wearing - How many brains they've had eaten - What their favorite color is
Here's some methods the zombie might have: - Eat brains - Target a human - Walk around aimlessly - Sit down
Another example is a dog:
Here are some attributes the dog might have:
- Their name - Their breed - Last time they slept - Favorite chewtoy - Size - How many years old they are
Here are some methods the dog might have:
- Bark - Sleep - Annoy mailman - Fetch
When a function is defined on an object, like Dog.Bark()
, we call it a method.
The goal of OOP is to introduce objects as a level of abstraction. We don't need to care about how Dog.Bark()
makes the dog bark, we just know we want it to bark! OOP isfocused on what we want to solve, not how we want to solve it.
Maybe later on we want to change how we make the dog barks, so we merely edit the Dog.Bark()
method. Because other code calling Dog.Bark()
doesn't need to worry about how the method works, we don't need to change it at all.
Most OO languages have the notion of a class. A class can be thought of as a blueprint or factory to create objects. A Zombie
class can be used to create zombies, a Car
class cars, a Dog
class dogs. In a class definition, we specify what attributes and methods objects of that class should have. When we make an object from a class template, it is called an instance of that class. So a dog would be an instance of the Dog
class. There's no limit to the number of instances of a class; we could create ten zombies from a Zombie
class, and all of them would be instances of the Zombie
class.
For example, we might have this code:
local Dog = {} function Dog:Bark() -- Play sound! end function Dog:Fetch() -- Go fetch! end
That's great, but how do we actually create a dog from the Dog
class, and how do we set attributes? We need a constructor. A constructor defines how to create a new instance of the class.
local Dog = {} local DogMetatable = { __index = Dog } function Dog.New(Name, Breed) local NewDog = {} setmetatable(NewDog, DogMetatable) NewDog.Name = Name NewDog.Breed = Breed NewDog.FavoriteChewtoy = "" NewDog.LastTimeSlept = 0 NewDog.Size = 5 NewDog.YearsOld = 1 return NewDog end function Dog:Bark() -- Play sound! end function Dog:Fetch() -- Go fetch! end
To create a new dog, we'd call Dog.New()
, our Dog
constructor. As you can see, our constructor takes some arguments, and we set the dog's Name
and Breed
attribute based on arguments passed to the constructor:
local Pet = Dog.New("Mini-Avigant", "Golden Retreiver") local OtherPet = Dog.New("Fetcher", "German Shepherd") print(Pet.Name) print(Pet.Breed) print(OtherPet.YearsOld)
This will create two new dogs with the Dog
class, and they will have our provided name and breed. The other attributes were set by the constructor function.
But what's with this weird metatable stuff in our class? You should already know about metatables, and you should know that the __index
event is fired when we try to get the value of a nil
index, like so:
local OtherInfo = { OtherSecretMessage = "hello world" } local Info = { SecretMessage = "hi", } setmetatable(Info, { __index = OtherInfo }) print(Info.OtherSecretMessage) --> hello world
The __index
and __newindex
events can be set to either a function or a table. If set to a table, Lua will look the index up there.
So in our Dog
class, we set the metatable of the new dog. If an index can't be found inside NewDog
, it will be looked up in the Dog
class itself. This allows us to define methods of Dog
outside of our constructor.
a:b(c)
is just syntactic sugar for a.b(a, c)
.
function a:b(c) -- Code end
Is syntactic sugar for this:
function a.b(self, c) -- Code end
c
is just a normal parameter, we don't need it. self
is an implicit parameter when using the colon syntax to define a function.
An example would be that workspace.Part:Destroy()
would be sugar syntax for workspace.Part.Destroy(workspace.Part)
. Of course, we could pass another argument that would be given to the implicit self
parameter, like workspace.Part.Destroy(workspace.Baseplate)
, and it would destroy the baseplate.
So in our Dog
class:
function Dog:Bark() print(self) --> Our `NewDog` table end
Is really syntactic sugar for:
function Dog.Bark(self) print(self) --> Our `NewDog` table end
The colon syntax is very convenient, and we'll use it to define methods. Our Dog.New()
function is not actually a method, because it does not represent the behavior of an individual Dog
instance, so we do not use the colon syntax.
Classes can inherit behavior from other classes. For example, a Dog
class might inherit behavior from an Animal
class. A Zombie
class may inherit behavior from a Creature
class. A class's inheritor is said to extend the class (Zombie
extends Creature
), and the class that is extended by another is said to be the other class's superclass (Creature
is a superclass of Zombie
). There's no limit to how deep a class's inheritance structure can go. Dog
could extend Animal
, which could extend LivingThing
, which could extend GameThing
.
We can implement inheritance with metatables. Once again, we can use __index
.
Here's our Animal
class:
local Animal = {} function Animal.New(Name) -- Remember that `setmetatable()` returns the table the metatable was set on, in this case an empty table. local NewAnimal = setmetatable({}, { __index = Animal }) NewAnimal.Name = Name return NewAnimal end function Animal:GetName() return self.Name end
Here's our Dog
class:
local Dog = setmetatable({}, { __index = Animal }) function Dog.New(Name, Breed) local NewDog = setmetatable({}, { __index = Dog }) NewDog.Name = Name NewDog.Breed = Breed NewDog.FavoriteChewtoy = "" NewDog.LastTimeSlept = 0 NewDog.Size = 5 NewDog.YearsOld = 1 return NewDog end function Dog:Bark() print("Bark!") end function Dog:Fetch() print("Fetch!") end
Now let's create a new Dog
and call some methods:
local Pet = Dog.New("Chow", "Golden Retreiver") Pet:Bark() print(Pet:GetName())
So Pet
is the table returned by Dog.New()
. When we look the key Bark
up inside the table, it doesn't exist, so it invokes the __index
event, which looks the key up inside Dog
. It exists, and the function Dog:Bark()
is called.
Next, we call Pet:GetFullName()
. First, it checks the table returned by Dog.New()
, where the GetName
key is not defined. Next, it checks Dog
, and it isn't defined there either. Because we set __index
on the Dog
class, it will next look the key up in Animal
, where it does exist.
Remember that:
Pet:GetName()
Is simply sugar syntax for this:
Pet.GetName(Pet)
Pet
will be argument to the implicit self
parameter in our definition of Animal:GetName()
. So it returns self.Name
, which is "Chow".
Our method of inheritance only supports single inheritance, where one class can inherit from at most one class. Single inheritance is how most class-based OOP languages choose to implement it. Notably, a few languages such as Python and C++ support multiple inheritance, where classes can inherit from multiple classes. In Lua, it's completely up to you which method of inheritance you want to choose.
I recommend you first learn about the diamond problem and how some languages solve it before attempting multiple inheritance.
When you're working with the Roblox API, you're working with OOP, you just don't know it. We create instances of classes with Instance.new()
. When we check if Instance:IsA("BasePart")
, we're checking if a part is a BasePart
, the superclass of all part classes (such as UnionOperation
and Part
and WedgePart
and so on).
When you call CFrame.new()
, you're calling the constructor of the CFrame
class. When you call workspace.Baseplate:Destroy()
you're calling the Instance:Destroy()
method (remember, superclasses).
I personally don't use OOP in Lua, but many developers find it allows them to write better code. For example, your SpitterZombie
, BoomerZombie
, TankZombie
and so on classes could all inherit from Zombie
.
I wouldn't say you should be using OOP though, and I definitely find I'm still able to write good-quality code without it.
I'd encourage you to try it. Nevermore is one example of OOP being implemented in Lua on Roblox, but there are many more you can find.
I just scratched the surface of OOP. I'd have to spend hours to explain everything. There's a lot of different topics you should learn about if you're interested in diving deeper:
JavaScript
adopted, and you can do this in Lua too)
Locked by Zafirua, Avigant, LifeInDevelopment, Gey4Jesus69, and Prestory
This question has been locked to preserve its current state and prevent spam and unwanted comments and answers.
Why was this question closed?