Object Oriented Programming (OOP) Advanced Inheritance

First, Im not english native, and my english couldnt be 100% correct

Over the time I learnt and used OOP classes in roblox using metatables, it was always a pain in the head to create some kind of superclasses that would work almost like roblox superclasses, and also were easy to set it up.

It was relatively easy to inherit methods, but for properties that wasnt the case, I was copy-pasting the properties because I was lazy. that would work if you dont have that many classes, but if you have 50 weapons that inherits a movement and shooting behavior it would be a nightmare to add or rename a property.

So I just made a class setting that works like roblox superclasses
I created this post so people can use it, I can get/know feedback/bugs, and you can comment more ways to achieve this so it’s a win-win situation

First let’s create a superclass as I would do

This is the Class Module

local Superclass = require(Classes.Superclass) --This is the superclass that has "to inherit" methods
local Class = {}
Class.__index = Class
setmetatable(Class,Superclass) --Inheriting

function Class.new()
    local self = setmetatable({}, Class)
    self.Property = "This is supposed to be only in this class"
    self.Superproperty = "This is supposed to be in the Superclass and inherit it to this class"
    return self
end
function Class:Destroy()
   --Clean up
end
return Class

This is the Superclass Module

local Superclass = {}
Superclass.__index = Superclass

function Superclass:Supermethod()
    --This method is shared in the Class "Class" Module
end
return Superclass

So, we would like to inherit the property to the class.

My first thought was to just create also a Superclass.new() constructor, clone the table, and merge it with the class table, but it is a little code-smelly, so we need to try something else.

My second thought was to create a InheritedTable and use the metamethods __index to search for it and __newindex to set it, but it messes with our current __index for the class Methods, so we need to change it.

Also a good addition I wanted is to be able to set a Superclass to another Superclass in a chained inheritance.
I’m gonna call it “Megaclass”,and why not, add a :IsA() method like roblox, that support those superclasses

This is what I did:
Class Module

--This is the Class Module
local Class = {}
Class.__index = function(self,Index)
    if rawget(Class,Index) then --We use rawget to bypass metatables and avoid stackoverflow
        return Class[Index] --Returning normal methods classes
    elseif rawget(self,"_Inherited") and self._Inherited[Index] then --Checking the superclass
        return self._Inherited[Index]  --Returning the Inherited property
    else
        return nil
    end
end
Class.__newindex = function(self,Index,Value) --Setting to Inherited properties
    if self._Inherited and self._Inherited[Index] then
        self._Inherited[Index] = Value --Setting values to the inherited table
    else
        rawset(self,Index,Value) --Setting Values to the table (normal table behavior)
    end
end
Class.ClassName = "Class" --The ClassName name (Optional, used in :IsA)
Class.Superclass = require(Classes.Superclass) --Superclass (Change it to your superclass module)
--You can set Class.Superclass to nil or delete this line if you dont want a superclass

if Class.Superclass then setmetatable(Class,Class.Superclass) end --Inheriting Methods

function Class.new(...)
    local self = setmetatable({}, Class)
    if Class.Superclass and Class.Superclass.new then --if superclass then creating a new superclass class and setting the _Inherited table with it
        self._Inherited = Class.Superclass.new(...)
    end
    self.Property = "Property" --This is a property for this class
    return self
end
function Class.isA(ClassName) --isA function
    if(Class.ClassName == ClassName)then
        return true --Classname match return: yes
    elseif(Class.Superclass)then --It has a superclass
        return Class.Superclass.isA(ClassName) --Recursively calling Superclass.isA()
    else
        return false --Not match
    end
end

function Class:Method() --This is a class method
    print("This is a method:",self.Property)
end
function Class:PrintBoth() --Method for checking if it can read both properties using self
    print("Property",self.Property)
    print("Super",self.Superproperty)
end

function Class:IsA(ClassName) --:IsA() method inside of the class calling .isA function inside of the Class metatable
    return self.isA(ClassName)
end
function Class:Destroy()
   --Clean up
end

return Class

Superclass Module

--This is the superclass module. It uses the same base code as the other one
local Superclass = {}
Superclass.__index = function(self,Index)
    if rawget(Superclass,Index) then
        return Superclass[Index]
    elseif rawget(self,"_Inherited") and self._Inherited[Index] then
        return self._Inherited[Index]
    else
        return nil
    end
end
Superclass.__newindex = function(self,Index,Value)
    if self._Inherited and self._Inherited[Index] then
        self._Inherited[Index] = Value
    else
        rawset(self,Index,Value)
    end
end
Superclass.ClassName = "Superclass"
Superclass.Superclass = nil --We dont need to inherit another superclass (you could tho) so you can delete this line

if Superclass.Superclass then setmetatable(Superclass,Superclass.Superclass) end

function Superclass.new(...)
    local self = setmetatable({}, Superclass)
    if Superclass.Superclass and Superclass.Superclass.new then
        self._Inherited = Superclass.Superclass.new(...)
    end
    --Here you can set more properties that can be inherited
    self.Superproperty = "Superproperty" --This is a shared property for this superclass
    return self
end
function Superclass.isA(ClassName) --This is recursively called
    if(Superclass.ClassName == ClassName)then
        return true
    elseif(Superclass.Superclass)then
        return Superclass.Superclass.isA(ClassName)
    else
        return false
    end
end

function Superclass:Supermethod() --This is an inherited superclass Supermethod
    print("This is a supermethod:",self.Superproperty) --printing inherited property
end

function Superclass:IsA(ClassName)
    return self.isA(ClassName)
end
function Superclass:Destroy() --We dont need this
   --Clean up
end

return Superclass

Let’s do some testing

local NewClass = require(script.Parent.Classes.Class).new()
print("Class Created")

--Reading properties testing
print(NewClass.Property)
print(NewClass.Superproperty)
--Methods testing
NewClass:Method()
NewClass:Supermethod()
NewClass:PrintBoth()
--Setting properties testing
NewClass.Property = "PropertyChanged"
NewClass.Superproperty = "SuperChanged"
--Reading again to check
NewClass:PrintBoth()
print(NewClass.Property)
print(NewClass.Superproperty)
--Testing :IsA()
print(NewClass:IsA("Class")) --true
print(NewClass:IsA("Superclass")) --true
print(NewClass:IsA("Foo")) --false

Here are the results:
image

So, It works but, I know what you are thinking, this looks like a mess, do I really need 30 lines only for setting it up? it will get even worse when I add new properties and methods.

Well…, technically yes but it doesnt need to look like a mess, you can delete any whitespace to make anything you dont need to change (or see) one line, or, to make it even better you can add tabulations so the functions dissapear to the side

Here’s how it ends up
image

Yes. It works the same. Trust me I tested it

Here you can get the compressed code for creating a class

Aditionally. Here you can get the VSC snippet which I personally think is the best way to use it (It’s used typing “aclass”)

That’s all, let me know what you think about it, if you use superclasses in your workflow, and if you have some feedback or a different option you use

Thanks for reading. Goodbye <33

13 Likes

OOP is supposed to make the code more clean and easier to maintain.
Implementing inheritance in Lua makes it rather more complicated and messy, that’s why I don’t use OOP while developing on Roblox.
However, it’s a great tutorial for people interested in this kind of features.
Good job. :slight_smile:

2 Likes

It is something I thought and I was scared to try it but you need to try it and get used to it, but OOP and inheritance is a lot easier and cleaner to work.

For example as I said I have a FPS gun script.
I need a movement class, a shooting/ads class and a melee attacking class.
The firegun inherits the shooting and the movement.
The melee inherits the movement and attacking.
and a rocket launcher inherits shooting/movement but you can replace the Shoot function and delete ADS, no more duplicated code, its pretty handy, plus this makes you able to work with vsc which makes you more professional, plus you can just copy my code and you have it really easily and visible, you dont need to maintain it since it works I tested it

Coming to it to this day, this is kinda of an issue i have, how would i go about making something like a combat system but separated in categories, can be another fighting style for every other player, how would i go about making all the User Input link up with the module script, have efficient functionality?

Cool.
Informative and well-structured.

Also, surely you could have made those pastebin links near the end an embedded code? :joy: