Singleton versus Table with Methods: Preference or functional difference?

Right now, I wish to set up some logic for my game. I’m really picky and indecisive about my coding conventions, so this topic mainly pertains to that. I want to know whether creating a singleton or returning a table with methods has any functional difference.

I am creating a CollisionsManager module within StarterCharacterScripts that is only accessible to the server (clients requiring the module will be returned with false). This much is fine - I require it to be a ModuleScript because I want a centralised way to edit the collision groups of a character without needing to set up such logic in several places at once. The module should be running some initialisation code upon first require, then exposing functions for modifying collision groups.

Now here’s my roadblock. I don’t know whether setting up my module as a singleton or as a table with methods in it has any difference or if it’s up to my own preference.

Table with Methods

-- Module
local CollisionsManager = {}
CollisionsManager.CurrentGroup = "Default"

function CollisionsManager:SetNewGroup(Name)
    self.CurrentGroup = Name
end

return CollisionsManager

-- Script
local CollisionsManager = require(path_to_module)

CollisionsManager:SetNewGroup("Group1")

Class Singleton:

-- Module
local CollisionsManager = {}
CollisionsManager.__index = CollisionsManager

function CollisionsManager.new()
    local self = setmetatable({}, CollisionsGroup)

    self.CurrentGroup = "Default"

    return self
end

function CollisionsManager:SetNewGroup(Name)
    self.CurrentGroup = Name
end

return CollisionsManager.new()

-- Script
local CollisionsManager = require(path_to_module)

CollisionsManager:SetNewGroup("Group1")

Is there really any difference between the two? I’d like to make full use of the benefits of ModuleScripts and try changing up how I code - this is one of those things.

2 Likes

I’m not certain, but I would use the Table with Methods.

First and foremost, I prefer that style. It’s easier to read and understand, and it’s shorter and cleaner.

Secondly, and this is a guess, I’m assuming that not having to set and use metatables is slightly faster.

2 Likes

I would also use a table with methods, since that’s all a Lua ‘object’ is anyway: a table with methods, properties and other stuff inside (albeit through a metatable)

1 Like

As far as organisational difference, personally I’d pick the former as well. Though, if there is a speed difference, is that difference negligible? I feel like table with methods versus a singleton is up to preference more than having an actual functional difference.

1 Like

I’ll try testing. I predict the difference won’t be enough to matter.

Edit: Results

Don’t worry about the performance, just use the method you feel more comfortable with.

2 Likes

It really depends on what you’re trying to do.

In my game framework, devs can create services/controllers in a singleton format, because that makes most sense. You don’t want to instantiate many of them, because they serve a single purpose on its own.

However, the framework also lets you create classes that can be instantiated, which works well in other circumstances, especially when trying to reflect in-game objects.

Here’s an example: If you want a module to handle loading maps/rounds, it makes sense to exist on its own, and thus it makes sense as a singleton. But you might also have a module to represent powerups dropped in the maps, which makes most sense to be represented as individual instantiated objects, and thus a proper “class” makes most sense.

7 Likes

Neither; For a singleton I’d dedicate a module script to it and make all of the state upvalues instead of using self and methods. If you use self, then anyone with access to the table can change internal state. Its like private vs public values, the principle of least privilege says make variables private unless you have a reason not too. Accessing upvalues is also faster than table access because upvalues are translated into specific locations before your code is run whereas table accesses are dynamic, on-the-fly hash map lookups. Not to mention that there isn’t any self and : magic going on for those not familiar.

It also avoids the heinous deed of using anything OOP related; it becomes a library of functions. To make it purely functional, the caller would pass in any needed state (like a list of parts for the CollisionsManager) and nothing would be stored inside of the library. This is more important when designing multi-threaded systems, but has its benefits in RBXLua too (namely simplified debugging, high modularity, increased flexibility, and easier code reusability.

2 Likes

This is the point. One thing I didn’t include is the top of the script that returns false on require. It’s only meant to be used by the server.

I’m also not quite sure I follow your response, outside of that.