All about Object Oriented Programming

This tutorial does include a section on inheritance, see “What about inheritance?”. As for object composition this just means containing other objects inside your object which is easily done in the constructor, for example.

local Inventory = require(...Inventory)
local Appearance = require(...Appearance)

local Player = {}
Player.__index = Player

function Player.new(name, userId)
    local self = setmetatable({}, Player)
    
    self.name = name
    self.userId = userId
    self.inventory = Inventory.new()
    self.appearance = Appearance.new()

    return self
end

As Lua’s metatable system is incredible flexible you are able to achieve almost whatever functionality you want, including polymorphism. This is out of scope for this tutorial as the implementation is quite dependant on what you wish to do but I’ll show some basic concepts you could try.

If you wish to convert an object to another class you can directly set it to another metatable ie setmetatable(object, OtherClass). This is dangerous though as you change the class of this object for every reference to it.

If you only wish to call another class’s function on it, you can do so like this OtherClass.method(object, args). This uses the underlying syntax of methods hiding the passed self reference, ie object:method(args) == object.method(object, args).

For more complex use cases you’ll want to make a more robust OOP framework with stronger information around typing which is too much to go into detail here. Hope this helps.

4 Likes

I guess when I think of composition I think of being able to implement multiple interfaces rather than inheriting from a single base class.

Not sure if this is the best example, but a class (NPC) could implement IMeleAttack, ICast, IRangeAttack.
Not a big deal if not supported, but to me that’s really OOP more so that inheritance.

Uhm, I kinda have a problem here. I followed your tutorial and I tried to create my own character class, along with a rogueclass. However, When I followed all the steps the output displayed an error which said the following, “ServerScriptService.Character.RogueClass:10: invalid argument #1 to ‘setmetatable’ (table expected, got nil)” Is there any solutions to this?

Edit: here is the script

local Character = require(script.Parent)
local RogueClass = {}

RogueClass.__index = RogueClass

setmetatable(RogueClass, Character)

function RogueClass.new(health, armor, speed, ability)
	local newrogue = Character.new(health, armor, speed)
	
	setmetatable(newrogue, RogueClass) -- This is where the error happened at.
	newrogue.Ability = ability
	
	function RogueClass:DealDamage(initaldamage)
		health = health - initaldamage
		print("Dealt Damage: ".. initaldamage)
		
	end
	
end

return RogueClass

This is probably the best tutorial on oop I have ever seen before ,I always had my doubts on it oop was useful or not but this definitely shows how nice it is! Thank you for this topic.

4 Likes

It’s probably because in your character module CharacterClass.new() function, you forgot to say ‘return newCharacter’.

In the same way you forgot to ‘return newrogue’ at the end of RogueClass.new()

2 Likes

Let’s take your car example. With the way you set up the metatable, would it mean that the car object would also technically have the .new() function? so from a new car object, we could call .new on the car object to create another new one? If so, is this intentional, or just something to ignore?

example of what I’m talking about:

local Car = require(game.ServerStorage.Car)

local newcar = Car.new(Vector3.new(1,0,1), "Guest1892", game.ReplicatedStorage.F1Car) -- creates new car
local newcar2 = newcar.new(Vector3.new(10,0,50), "Guest2456", game.ReplicatedStorage.F1Car) -- creates another new car from the previously created car???

Hmm. That’s a good question. However, I don’t really have the solution to this problem. But, it is rightful to assume that it doesn’t work. The reason why is because when the newcar.new is called, because it is inside of the constructor, it will probably not work. However, this is spectulation. what you can do to test this is probably do


print(Driver) 

inside of the constructor to compare whether it works or not.

Absolutely amazing documentation! OOP has really let me expand my sights and explore things that I may have before deemed completely impossible. You write it in such a clear and professional way, and answer almost every one of my questions before I can even ask them! Astounding!

1 Like

I honestly wish Lua or Roblox would add something to make objects a little more easier for people who like the way that Java and Javascript do it, nice tutorial though

1 Like

Is there a way to reference the class of an object? Say you want to reference a certain ‘car’, then you want to see which classes/subclasses it is a part of. For example, a truck would be able to reference the truck class and the car class, while a car would be able to reference the car class. Is there a way to do this?

So you can do a lot with less simplified structures.

I have separated the data model (Table) and the corresponding functionality (MetaTable) as well as class definition (Class Table with constructor)
A truck obj has no reference for making a new truck.

I have also used the function version of __index instead of the shortcut table syntax
This allows me to check for specific cases and handle them differently while defining them within the Inheritor.

Inheritance Repro
local Car = {} 

Car.proto = { -- Default Car Values
	["Position"] = nil;
	["Driver"] = nil;
	["Model"] = "Basic Car";
	["Speed"] = 10;
}

Car.mt = {}--Car class MetaTable holds all the functions of cars 

Car.mt.__index = function(tabl, key)
	if key == "Class" then  		--If you want the class, return name of class
		return "Car"
	elseif Car.proto[key] then 	--If key is a value of car then return default
		return Car.proto[key]
	else 												-- otherwise it is a function of car or nonexistant(nil)
		return Car.mt[key]
	end
end

--Constructor
function Car.new(position, driver, model) 
	local newCar = {}
	setmetatable(newCar, Car.mt)
	
	newCar.Position = position
	newCar.Driver = driver
	newCar.Model = model
	
	return newCar
end

function Car.mt.Boost(car) --adds speed to a car
	car.Speed += 10
end 



local Truck = {} 

Truck.mt = {}--Truck class MetaTable
setmetatable(Truck.mt,Car.mt) -- Trucks inherit Car functions
Truck.mt.__index = function(tabl, key)
	if key == "Class" then -- If you want Class, return Class, and any higher classes
		return "Truck, " .. Truck.mt.Class
	else -- otherwise it is a function or nil
		return Truck.mt[key]
	end
end

--Constructor
function Truck.new(position, driver, model)
	local newTruck = Car.new(position, driver, model) -- just a car + some
	-- add special data for truck (maybe storage?)
	setmetatable(newTruck, Truck.mt) -- add functions to the truck
	
	return newTruck
end



local MonsterTruck = {}

MonsterTruck.mt = {} --MonsterTruck Functions
setmetatable(MonsterTruck.mt,Truck.mt) -- MonsterTrucks inherit Truck functions
MonsterTruck.mt.__index = function(tabl, key) --if key is not in object(MonsterTruck)
	if key == "Class" then  -- If you want Class, return Class, and any higher classes
		return "MonsterTruck, " .. MonsterTruck.mt.Class
	else 										-- otherwise it is a function or nil
		return MonsterTruck.mt[key]
	end
end

--Constructor
function MonsterTruck.new(position, driver, model)
	local newMonsterTruck = Truck.new(position, driver, model) -- just a truck + some
	-- add special data for monstertruck (maybe tire size?)
	setmetatable(newMonsterTruck, MonsterTruck.mt) -- add functionality to your obj
	
	return newMonsterTruck
end

local myMonTruck = MonsterTruck.new()
print(myMonTruck.Class)
print(myMonTruck.Speed)
myMonTruck:Boost()
print(myMonTruck.Speed)
local dup = myMonTruck.new()
print(dup)

I like the tutorial, however personally I prefer Java’s approach to OOP.

I wonder if someone would make a thing comparing it to other languages?

Hey there!
While Java has a class-based object oriented programming style, where classes can extend and implement others, LUA (unfortunately) does not. Using metatables in LUA is unfortunately as close as we can get to Java’s class-based style, since LUA does not have a built-in class framework.

Hope this helps!

3 Likes

Thank you so much! I do have one question, can something like a static variable or instance variables be made?

Yep! You can create static variables by just setting defining the variable. You can do this by (in the case of using a ModuleScript) writing code on the lines of this.

local Class = {}
Class.__index = Class

Class.staticVariable = "Hey there! This is a static variable" -- A static variable that can be accessed from anywhere, and doesn't change when you make an instantiated class unless you manually change it with "Class.staticVariable = ..."

function Class.new()
    local self = setmetatable({}, Class)
    
    self.classVariable = "Hey there! This is a class variable" -- A variable that only the instantiated version can access.

    return self
end

Hope this helps! :smiley:

3 Likes

Is it possible to make a default constructor? For example in Java you would have

public class Car {

String carModel;
//Default Constructor
public Car(){
 carModel = "";
}
//Constructor
public Car(model){
 carModel = model;
}


}

Is it also possible to make objects send to multiple scripts or in a value?

Lua doesn’t have any function overloading, so there is usually only 1 constructor.

2 Likes

You can write one function to use different combinations of variables though.

How would I go about putting multiples of different objects from the same module scripts in tables? From what I have tried it overwrites the previous object.

ModuleScript:

local slot = {}

slot.__index = slot

function slot.new(index)
	local newSlot = {}
	setmetatable(newSlot,slot)
	slot.Index = index
	
	return newSlot
end

function slot:getName()
	return self.Index
end

return slot

Main:

local mod = require(game.ServerScriptService.Slot)
local obj1 = mod.new("Name")
local obj2 = mod.new("Name2")
local array = {obj1,obj2}
print(array[1]:getName())

The output says “Name2” instead of “Name1”