Singleton vs Table with Members: What, when and why?

I had a shower thought moment and felt I wanted to get an answer for this question.

Singleton:

local Singleton = {}
Singleton.__index = Singleton

function Singleton.new()
    local instance = setmetatable({
        something = "Nothing"
    }, Singleton)
    return instance
end

function Singleton:GetSomething()
    return self.something
end

return Singleton.new()

Table with Members:

local MyItems = {
    something = "Nothing"
}

function MyItems:GetSomething()
    return self.something
end

return MyItems

To my understanding, choosing to use either of these is completely preferential. That then brings me to the question: has anyone experienced a case where it wasn’t a matter of preference? Do you use singletons and if so, what for? Why them over a plain table with elements and member functions? What would be an appropriate circumstance to use them in?

4 Likes

The first implementation is the only one that would ever really have a different use because a metatable can imply there’s some kind of special operator overloading or logic that runs on certain operations. The second implementation is usually new users who want to write OOP might write thinking it’s what it’s all about.

So really you could go for either the first implementation if you want that special logic or just a table with functions if you want an isolated “instance” that could potentially mutate itself. Just never the second.

1 Like

I don’t think these are all that different though? The second one looks more like an improper implementation of OOP, but the code itself is still valid to write nonetheless.

I don’t follow when you refer to “special operator overloading”, “logic that runs on certain operations” or “special logic”. Does this have to do with the metatable itself or the overall Singleton concept? I’m 5 years old when it comes to this kind of thing.

Yes, it has to do with the metatable itself. Operator overloads allow you to change what happens to the object when an operation is performed on it. As an example, you could overload the call operator to concatenate whatever argument you supply to “something”. Here’s a code sample that does just that:

local Singleton = {}
Singleton.__index = Singleton
Singleton.__call = function(self, value)
    self.something = self.something .. value
    return self
end

function Singleton.new()
    local instance = setmetatable({
        something = "Nothing"
    }, Singleton)
    return instance
end

function Singleton:GetSomething()
    return self.something
end

local singleton = Singleton.new()
print(singleton("Something"):GetSomething()) -- >NothingSomething

(I’m on mobile, so sorry if this doesn’t work, but you get the idea)

I wouldn’t say this is as preferential as it is based on use case (whether you need to overload), and having a constructor just makes it easier to manage multiple singletons with the same set of operator overloads.

It doesn’t make a lot of sense to build a singleton as a pseudo-class in Lua. The only reason they’re structured that way in OOP languages is because it’s the only way to do it.

IMO, I would stick with the simple table w/ methods. I do this in my game framework and it works really well. I still throw some metatable stuff in the mix, but minimal.

I’m sure there are counter-arguments though.

2 Likes

So… overloading is just… metamethods?

local MyItems = {
     something = "Nothing"
}

function MyItems:GetSomething()
    return self.something
end

-- No particular reason why I wrote it like this as an example
local Metatable = {}
Metatable.__index = Metatable
Metatable.__metatable = "No"

function Metatable:__call(value)
    self.something = self.something .. value
    return self
end

setmetatable(MyItems, Metatable)

return MyItems

It seems that what I can do for a OOP singleton, I could do without adopting the OOP paradigm and making a singleton class. Is this not the same overloading behaviour? If it isn’t, how is it different? Where then does specialty lie between this over a singleton? Organisation? Preference? Some kind of benefit or functional difference?

Also, where do you get multiple singletons from? To my knowledge, the point of a singleton is that only one instance of a certain class exists at any given time. Is this in regards to other singletons?

1 Like

As @Crazyman32 stated, usually you most likely wouldn’t need a pseudo-class, so it really wouldn’t make sense to use one unless you need to be able to manage multiple singletons (yes, other singletons that can all be different after constructed) from the same general constructor.

1 Like

If you really want to get picky about functionality, using the weird OOP amalgam limits your ability to use methods independently. Maybe you have a function that takes another function, like table.sort. If your function is defined as a method when it doesn’t need to be, you’d need to wrap the function which is extra steps.