Object-Oriented Programming -- or OOP -- is simply a programming model where all kinds of data (be it Bools, Strings, etc.) is encapsulated into an object. In other words, you are essentially bundling up information under a single unit that can later be manipulated by the object's function operations or methods that you create.
The thing with OOP is that you'll be able to create classes of your own, hosting certain behaviors and function operations which future objects can then extend from or inherit. You can pretty much think of this as something cloning another, with the ability to do exactly what its counterpart is able to do, but with a twist of its own. Here's some code for visualization:
Say a random crayon had magically appeared in our hand. It just so happened to be red, dull, and probably 2 inches after being used so much.
local RedCrayon = {
Color = "Red",
Condition = "Dull",
Length = 2
}
From what we know with our coloring activities in the past, our crayons weren't always red or 2 inches long and dull. We sometimes had blue crayons, purple crayons, and even in the color black. On top of that, some were a lot longer, or even extremely shorter than others, with a mix of sharpness or dullness.
But from just this poor excuse of a red crayon, how can we tell exactly its original shape, color, or condition? It had to have come from somewhere, right? From a default set of data.
local Crayon = {
Color = "White",
Condition = "Sharp",
Length = 5
}
This is a class with default properties that can be inherited by objects (in this case, crayons!).
--
Crayons can also color on things when physically contacting another surface.
function Crayon.ColorSomething()
Crayon.Length = Crayon.Length - 1
Crayon.Condition = "Dull"
end
But wait! If we have a function or method operating on the table (Crayon) in the global scope, we would end up modifying the default values! If future objects end up inheriting from that same table we accidentally operated on, they would be affected too.
This is where we include a self parameter; to give our objects a selfness to them; to say that, "hey, these are my values, and only I can act on them."
function Crayon.ColorSomething(self)
self.Length = self.Length - 1
self.Condition = "Dull"
end
Now, when calling the ColorSomething() method, objects will only be able to manipulate its own lengths and conditions. Not the class's, nor any of the other crayons: only its own.
self is talking about the object that fired the method. If I had a purple crayon, and did:
function PurpleCrayon.ColorSomething(self)
self would be equal to PurpleCrayon.
--
Going back to our red crayon, you can probably already tell that it's been dull ever since we got it. Now if only there was some way to sharpen it, along with some of the other dull crayons laying around. Maybe the Crayon class has something to offer us?
function Crayon.Sharpen(self)
if (self.Condition == "Dull") then
self.Condition = "Sharp"
end
self.Length = self.Length - 1
end
Yes! That's exactly what we needed! All we need to do now is execute that command from our red crayon.
RedCrayon:Sharpen()
print(RedCrayon.Condition)
It will print "Sharp" in the output. It looks like we did it!
But you're probably wondering: why are the parentheses empty? Isn't there supposed to be an argument to pass as self? Nope. That's how an object's selfness works. The method cannot be used to modify any other object but itself. You can use the colon operator (:) to hide this self paramater, without changing the definition of the function.
function Crayon:Sharpen()
if (self.Condition == "Dull") then
self.Condition = "Sharp"
end
self.Length = self.Length - 1
end
But that's just syntactical sugar. You're free to use either a dot or colon.
It's good we have all of these behaviors and all, but what good are they if we can't create new crayons or instantiate them?
Before proceeding, it's good to know that, unlike other familiar programming languages like (but is not limited to) Javascript, Lua does not use any built-in constructor functions that identifies and/or defines an object. That's because it isn't an OO language. We are simply mimicking OOP (call it fake OOP if you will) using what's available to us, thanks to Lua's extensibility: Metatables and Metamethods.
For the sake of introduction, we'll only be focusing on how to create an object using setmetatable() function and __index metamethod as the final part of this lesson.
Here's what a typical instantiation function looks like, using our crayons example:
function Crayon:New(Color)
local Object = {}
Object.Color = Color
self.__index = self
setmetatable(Object, self)
return Object
end
setmetatable(Object, self) is basically read as, "The metatable of Object is self." (Using what we learned from earlier, self is equal to Crayon in this case)
And now you're probably wondering, what is __index? Why did you set the __index of self to self?
"__index" in this example, in conjunction with setmetatable() tells the code to look for a value in self IF the value cannot be found in the object itself, represented by table Object. This is how inheritance works.
To elaborate further, I'm sure you've already noticed that only the Color property is being set by the instantiation function. But if we tried printing the Condition property of a new object, it'll output "Sharp", which, surprise surprise, is the same as the default value set in the Crayon table. That's because the code was told to look at the metatable (Crayon) since we never set a Condition value in our new object.
--
In regards to the function itself, you don't actually have to name the method as "New." It could be anything; it's only to identify it as the instantiator. Here are some ways of implementing an instantiation function:
function Crayon:Create(Color)
local self = setmetatable({}, {
__index = Crayon
}
self.Color = Color
return self
end
function Crayon._make(self, Color)
return setmetatable({
Color = Color
}, {
__index = self
})
end
Now you're probably curious on how this would all look in one, big chunk of code.
local Crayon = {
Color = "White",
Condition = "Sharp",
Length = 5
}
Crayon.__index = Crayon
function Crayon:New(Color)
local Object = {}
Object.Color = Color
setmetatable(Object, self)
return Object
end
function Crayon.ColorSomething(self)
self.Length = self.Length - 1
self.Condition = "Dull"
end
function Crayon:Sharpen()
if (self.Condition == "Dull") then
self.Condition = "Sharp"
end
self.Length = self.Length - 1
end
And that's pretty much it for your Crayon class (or object, if we're being technical.) All you have to do now is create a new object to extend from this class, otherwise all of this is just worthless.
local RedCrayon = Crayon:New("Red")
for i = 1, 3 do
RedCrayon:ColorSomething()
end
RedCrayon:Sharpen()
print(RedCrayon.Length)
If the Lua gods will it, this snippet of code should print a simple 1 from RedCrayon's Length property.
The best way of practicing OOP is just to apply it. Take it from me, reading documents over and over again will hardly get you anywhere; they're only useful to providing you with what you NEED to know. Play around with the code above, add your own behaviors, or even create an entirely a new class with unique behaviors using what you know so far!
Happy coding!