ClassUtil - An Object Oriented Utility Module

ClassUtil

ClassUtil is a utility module aimed for ease of use with object oriented programming in Roblox. It uses zero metatables (better performance!) and is easy to use! It offers an easy and clean way to handle class inheritance.
See Usage section below.

Installation

Option 1 - With RoStrap framework (what I personally recommend for organization)

  1. Install RoStrap (Getting Started - RoStrap).
  2. Run the plugin and select a framework location.
  3. Insert the module into the framework Packages folder.
  4. Use Resources:LoadLibrary(“ClassUtil”) to require it.

Option 2 - With require

  1. Insert the module somewhere
  2. Locate and require the module

Usage

Your class must use the following: ClassUtil:Class(MyClass, [ParentClass]). This creates a .new function for the supplied (or new combo) class. Your class must contain a :Constructor() method and it must be defined before ClassUtil:Class() is called in order for .new to work. The self argument of the Constructor function is the newly instantiated object (see below). The rest of the function arguments are passed from .new to the constructor function.

To create

local MyParentClass = {}
function MyParentClass:MyMethod(...)
	print("From MyParentClass", ...)
end
function MyParentClass:MyParentMethod(...)
	print("From MyParentClass (in parent method)", ...)
end
function MyParentClass:Constructor(...)
	self:MyMethod("Called by MyParentClass!")
	self.super:MyMethod(...) -- super still contains all MyClass methods and properties but not any inherited ones.
	self.super.super:MyParentMethod(...) -- super.super contains all parent (MyParentClass) class methods and properties but not any inherited ones.
end

ClassUtil:Class(MyParentClass) -- Must go after Constructor. Returns MyClass again.

local MyClass = {}

MyClass.ABC = 123

function MyClass:MyMethod2(...)
	print("From MyClass", ...) -- Prints arguments from MyClass.new(...)
end
function MyClass:MyMethod(...)
	self.super:MyMethod2(...) -- Super exists here too
end
function MyClass:MyParentMethod(...)
	error("You shouldn't see me because I'm overriden and the parent class is using super!")
end
function MyClass:Constructor(...)
	self.super:MyMethod(...) -- super contains all MyClass methods and properties but not any inherited ones.
end

local MyComboClass = ClassUtil:Class(MyClass, MyParentClass) -- Must go after Constructor. Returns copy of MyClass with its parent set to MyParentClass.

MyComboClass.new("abc123", "secondArg", 123)

Output:

From MyClass abc123 secondArg 123
From MyParentClass Called by MyParentClass!
From MyClass abc123 secondArg 123
From MyParentClass (in parent method) abc123 secondArg 123
6 Likes

What would be the best use case for this? Not fully understanding the concept.

1 Like

Instead of needing to write out a ton of metatable code you can use this module. (That also means slightly better performance when indexing classes)

It also handles inheritance and gives you access to inherited functions/static properties. This is basically a way to keep your object oriented code neat.

1 Like

I don’t understand why this is better than the classic

local Class = {}
Class.__index = Class

function Class.new(super)
	local self = setmetatable({
		["super"] = super,
		// other fields
	}, Class)

	return self
end

function Class:Method()

end

return Class

Is it that you don’t need to use a bunch of supers to access inherited methods and fields? Also, what makes your method faster than metatables?

Metatables invoke a slight performance cost since they need to be read from when a table is indexed and any functions you include need to be called. I also find having a bunch of setmetatable/getmetatable calls everywhere very ugly and hard to read especially with the extra lines added from creating metatables. It’s mostly a personal preference thing I suppose.

Also the super functionality I’ve added is useful in some cases (for example if you have an Item class and a Resource class which inherits from it you can access the Item’s methods AND the Resource’s methods easily without needing to handle self arguments yourself)

Example:

Resource:Pickup(player) -- Calls both functions
Resource.super:Pickup(player) -- Calls only Resource:Pickup
Resource.super.super:Pickup(player) -- Calls only Item:Pickup
2 Likes

That made me realize I was not merging functions so I went ahead and fixed that.
Inherited functions not accessed through super now properly merge.
The top function controls return values.