What is Object Oriented Programming?

Ive heard about OOP but i have no idea what it is and ive looked around and couldnt really find anything clear about it

can anyone explain?

2 Likes

if you made a gun system and found a bug among your code and you have 50 guns that all have the scripts in them, that would take a while, so instead you use something like a module to do the guns

General info

Object oriented programming is a programming paradigm, (cool fancy ‘synonym’ for style, other styles: functional, imperative, …) where pieces of code are instead seen as individual objects that each have a unique state. (what I mean by style is that code is organized in a certain way to represent something, in this case objects)

for example, imagine you have a chair object and you change its uhh Occupied boolean value to true, other chair objects won’t change because they are different objects and have different states.
Objects can contain methods (fancy words for class function), fields/properties (fancy word for class variable) and sub ‘types’. (so a new type of object inside an object, not really used a lot in lua, it is more of a statically typed thing.)

Objects come in the form of classes, interfaces & structures, but since lua is dynamically typed interfaces & structures aren’t needed so classes it is.

a class is basically a blueprint and each instance (fancy word for ‘copy’ of the object) follows the blueprint, so if you add a hello function to the blueprint all copies(instances) of that class will have that hello function.

you declare a class (yes that is what it is called when you create a class), and the action is called a class declaration.


OOP in lua

so let’s look at an example class in uhhh, well lua.
tw: since lua doesn’t have support for OOP you will need to use metatables which are special tables that give extra information about another table, using getmetatable and setmetatable you can uhhh, manage these.

--it is good practice to put class (bluprint) declarations inside of a modulescript
--just like you'd put them in separate files when working with TS, or cpp.
local person = {}
person.__index = person

function person.new(name :string)
 local new = setmetatable({
    name = name,
    age = 69,
   --...other fields that can be inside of a theoretical person object.
 }, person)

 return new
end

function person:Destroy()
 self.name = nil
 self.age = nil
end

return person

alright this is a basic example, but before I go into whatever this thing is, I’ll have to explain a bit about metatables,
so basically metatables as stated before provide more information about another table (keyword -meta which is often used in this context, eg: metadata).
but that is not the only thing they do, they also have special things called metamethods.

methamethods are special functions that are called when your table is used in a specific way.
they typically start with two underscores followed by what they do.
eg: the __tostring methamethod fires when your table is passed as an argument to tostring
there are a bunch of useful once like __add, __sub, __div and**__mul** which fire when their mathematical operation is performed on your table, (add for +, sub for -, div for / & mul for *).
aswell as __call for when your table is called like a function, or __concat when your table is included in a string concatenation operation using .. operator.
for more info on metamethods: Metatables | Documentation - Roblox Creator Hub

but some have more than one use, in the example above, you’ve seen __index, which fires when your table is indexed but the key doesn’t exist, if instead, you assign a table to __index lua will look through the other table when the key doesn’t exist, to see if it exists over there, if it does it is returned otherwise nil is returned.

local mytable = {}
mytable.__index = mytable
mytable.msg = "hello"

local mytest = setmetatable({}, mytable)
print(mytest.msg) -- prints hello!

why does this example above print hello even though the table mytest is empty?
that is because it notices that the index doesn’t exist, so it looks for** __index**, and finds it in the metatable mytable, it fires __index which points back to mytable and inside of mytable is something called msg, so that is returned instead.

mytest.msg -> my test doesn''t have msg, look for __index in metatable.
mytable.__index = mytable -> found it, it links to this table called mytable.
mytable.msg = "hello" -> wow, this table has the requested index, let''s return it!

print(mytest.msg) -> hello.

uhh, short diagram on what actually happens.
so the reason why this is used in lua OOP is because you can then define all methods inside of the class table, and you’d just have to link your instances to the class table using setmetatable() and boom, it will automatically call the correct function even if it doesn’t exist in the instance table.

that’s why it is good practice to store properties/fields inside of the instances, and methods inside of the class table.
okay, that explains this part of the code and why it exists:

local person = {} --class table
person.__index = person --link to the class table so that methods can be called from here

but what is this:

function person.new(name :string)
 local new = setmetatable({
    name = name,
    age = 69,
   --...other fields that can be inside of a theoretical person object.
 }, person)

 return new
end

well this is a special function (in other languages they are special, but in lua they are treated the same as normal functions, the use case is special).

it is called the constructor of a class, the constructor is basically the function that creates the instances of a class while following the blueprint, when called, it will return a new instance of the person class, so when you call person.new() you are ‘creating’ a new person.

let’s go over what is happening here, so first it declares ‘new’, which stores the new instance of the class, it then gives new all the properties/fields of the class because that’s good practice, some have default values but some are passed as arguments (eg: name).

then once it is done it links it to a metatable called person using setmetatable, which is the person class table, this is required because otherwise when we call a class function, it will error.
then it returns the new instance.

similarly, a class can also have a destructor which is the opposite of the constructor, it is tasked with cleaning up the instance and doing some final operations before it is destroyed.

function person:Destroy()
 self.name = nil
 self.age = nil
end

as you can see it is setting all properties to nil, but it is using a strange word… ‘self’?
self is a word you can use to refer to the table that this function got called on.
it is passed as a secret implicit argument, so destroy actually looks like this;

function person.Destroy(self)--notice the : is now a .?

the colon ( : ) is used to call functions like these, and it is just what they call syntatic sugar to make something look nicer.
here is example usage on how to instantiate (synonym for creating) objects of a class.

local classtable = require(person)--this is how you load modules
local instance = classtable.new("bob")

print(instance.name) -- bob
instance:Destroy()

what happens:

  • the module is loaded using require(), just like how any module should be loaded: ModuleScript | Documentation - Roblox Creator Hub

  • then the class constructor is called and it returns a new instance of the blueprint(class).

  • instance 's name is printed, __index is not triggered because .name is part of the instance.

  • destroy is called, it is not part of instance so __index fires which returns the destroy function of classtable then by using self the origjnal instance table is accessed and its properties are set to nil.
    this is what it actually looks like without the sugar;

    instance:Destroy() -- self is assumed to be instance
    ->
    instance.Destroy(instance) -- self is explicitly set to instance
    

at the end of the day, objects & classes and all of that, is just, a funny way of using tables,
that’s why:

instance["name"] = instance.name

so yes if you want, you can write self[“name”] instead of self.name because they are tables, similarly:

instance:Destroy()
--can be written as
instance.Destroy(instance)
--but also
instance["Destroy"](instance)

now, this was in a language that doesn’t support OOP, but let’s look at an example of a language that does, you don’t need to really know the language, since it should be clear what’s happening:

class Person {
  public:
   int age;
   std::string name;

  Person(std::string n) : name(n), age(69) {}
  Person() = default; //ignore this part, it is there to make it correct.
}

this is C ++, it is the same class as our lua person class from before, the constructor now being called Person() instead of new(), and a new word called public which doesn’t appear in lua…


OOP in general

this is no longer in lua’s scope so you can stop reading here, but in other programming languages that support oop you have something called Access Modifiers, which basically tells the program when and by whom a member of a class can be accessed.

the most used ones:

  • public, the member can be accessed by anyone, this is the lowest protection level.

  • private, the meber can only be accessed by code that runs inside of the class, so in our previous example it would theoretically be accessible inside of :Destroy() since it is part of our class, but not inside of our example script.

  • protected, same as private but it is also accessible in derrived classes.

now, what are derrived classes?
basically, OOP also has something called inheritance.
Imagine us humans, we evolved from ‘monkeys’ if you want to call it that.,
that means we inherited some features from them such as legs, arms, big brain & more.
we essentially derrived from them.

in programming, humans would be the derrived class while monkeys would be the base/super class.
consider this example in Cpp:

class Monkey {
 public:
  //just imagine these types exist for the sake of this example.
  Legs legs;
  Arms arms;
  Brain brain;
}
//this : access modifier classname is how you perform inheritance in c++
class Human : public Monkey {
  const char* name; //I got sick of writing std::
  int age;
}

this is the programming version of my previous example, as you can see we have a monkey class, and a human class, the human class inherits from the monkey class.
what this means is basically, all of the members that are available inside ‘monkey’ can be used inside of human.

so even though we haven’t explicitly added a property/field called legs, we can type human.legs without errors, because humans derrive from monkeys.

some rules to inheritance:

  • all members of the base class (monkey) are inside of the derrived class (human) but no members of the derrived class are inside of the base class

  • the derrived class is equal to the base class, but the base class is not equal to the derrived class: human = monkey, monkey ~= human.

this supports polymorphism (if i spelled that correctly), which basically means that one object can have multiple forms, in this case human is human but also monkey.

uhh, yeah so that’s basically all I have to say,
it was fun writing this, next time you should probably post this somewhere else, because I don’t think education support is a popula category.

if you have any questions, don’t hesitate to ask them!
hope this helps!

edit: fun docs on metatables & modules:
ModuleScript | Documentation - Roblox Creator Hub
Metatables | Documentation - Roblox Creator Hub
Metatables | Documentation - Roblox Creator Hub
Lua Globals | Documentation - Roblox Creator Hub

1 Like