Scripting Helpers is winding down operations and is now read-only. More info→
Ad
Log in to vote
6

What is Object Oriented Programming and how is it used? [closed]

Asked by 6 years ago

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.

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?

2 answers

Log in to vote
9
Answered by
Avigant 2374 Moderation Voter Community Moderator
6 years ago

Had to split this answer into two parts because it was too long

Part 1

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.

Abstractions

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.

What is Object Oriented Programming?

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.

Classes

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.

Ad
Log in to vote
9
Answered by 6 years ago
Edited 6 years ago

** This is part 2 of my answer check the first one first **

Interlude: Why we use colons

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.

More on Classes

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.

Inheritance

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.

You've Been Using OOP All Along!

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

Should You Use OOP?

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.

But Wait, There's More!

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:

  • Accessors and mutators (getters and setters)
  • Static methods
  • OOP design patterns
  • How to design classes and methods well
  • Prototypical OOP (prototype-based rather than class-based, this is the model JavaScript adopted, and you can do this in Lua too)
  • Much more

Resources

Programming in Lua chapter on OOP

Devforum guide "All About Object Oriented Programming"

2
Thank you so much! I will accept your first answer but I consider both to have helped immensely. User#21908 42 — 6y