Help me design a Singleton Mixin for middleclass?

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
Snackrilege
Prole
Posts: 19
Joined: Sat Sep 06, 2014 8:50 pm

Help me design a Singleton Mixin for middleclass?

Post by Snackrilege »

Hey all. I want to make a singleton mixin for all my singleton needs!


Here's what I've got so far -

Code: Select all

local class = require 'middleclass'

Singleton = {}

function Singleton:getInstance(...)
  if not self.class._instance then self.class._instance = self.class:new(unpack(arg)) end
  return self.class._instance
end
I still need to make sure an error is raised if you try to directly instantiate the class, and I'm not totally sure if I'm using the syntax correctly to make class variables. Oh well.

Honestly, its not critical, there are other ways to make a singleton, and I should probably be spending my time on other parts of this project. Still, its a fascinating problem.

If you have some time or some thoughts on design input, let me know!

And, of course, if this kind of thing already exists in a library somewhere -- definitely let me know :)
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: Help me design a Singleton Mixin for middleclass?

Post by kikito »

Here's how it could be done (Warning, I have not tested it):

Code: Select all

local Singleton = { static = {} }
function Singleton:included(theClass)
  theClass.static._instance = theClass:new()
  -- override new to throw an error
  theClass.static.new = function() error("Use " .. theClass.name .. ":getInstance() instead of :new()") end
end

function Singleton.static:getInstance()
  return self._instance
end

return Singleton
You can find more documentation about included here: https://github.com/kikito/middleclass/w ... s#included

Now I should retire to my cave, but I honestly feel I would be failing if I didn't give you some words of caution. Please feel free to ignore them.

A singleton, even if wrapped inside a class and a mixin, is just a global variable. Using this mixin is equivalent to doing a global variable like this:

Code: Select all

mySingleton = MyClass:new()
With a different syntax.

If you really want to go the Singleton route, I would recommend considering using globals instead; it's more honest and it'll give you visibility about what is "really being shared globally". Singletons will give you a false sense of security that will push you to share more than it's really necessary.

If you are trying to use Singletons because you are trying to avoid global variables, consider passing whatever you need as a parameter to the constructor of whatever needs it (that's an OOP principle, pass dependencies on the constructor).
Last edited by kikito on Wed Sep 10, 2014 11:04 pm, edited 1 time in total.
When I write def I mean function.
Snackrilege
Prole
Posts: 19
Joined: Sat Sep 06, 2014 8:50 pm

Re: Help me design a Singleton Mixin for middleclass?

Post by Snackrilege »

ahh, excellent.

The other benefit to singletons is lazy instantiation. That is to say, I can call it from anywhere as something like
local camera = Camera:getInstance()
In order to get the benefits of lazy instantiation, would this be a viable modification to your code?

Code: Select all

function Singleton:included(theClass)
  -- override new to throw an error
  theClass.static.new = function() error("Use " .. theClass.name .. ":getInstance() instead of :new()") end
end

function Singleton:getInstance()
  if not self.class._instance then self.class.static._instance = self.class:new()
  return self.class._instance
end
^the difference being that in my version, the singleton isn't instantiated until the first time getInstance() is called


without worrying about whether or not it has already been instantiated anywhere else yet. True, that could likely be done another way.

I will definitely keep your words in mind, though. I'll make sure I'm not super committed to the implementation so that I can still fix my silly experimentation later.

Thanks for everything, I'm a big fan.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: Help me design a Singleton Mixin for middleclass?

Post by kikito »

Snackrilege wrote:In order to get the benefits of lazy instantiation, would this be a viable modification to your code?
I don't think that would work. For two reasons. The first one is that I forgot to add "static" to the definition of getInstance, so it is an instance method (I just fixed it on my first answer). This means that 'self' will be "the class" - no need to do self.class._instance.

The second issue is that :new is defined to throw an error the moment the mixin is included in the class. As a result, calling :getInstance would raise an error.

You can still do lazy evaluation, but you would need to store a reference to the "old new method"; something like this should work:

Code: Select all

local Singleton = { static = {} }
function Singleton:included(theClass)
  -- override new to throw an error, but store a reference to the old "new" method
  theClass.static._new = theClass.static.new
  theClass.static.new = function() error("Use " .. theClass.name .. ":getInstance() instead of :new()") end
end

function Singleton.static:getInstance()
  self._instance = self._instance or = self._new(self) -- use old "new" method
  return self._instance
end
Thanks for everything, I'm a big fan.
Thanks! Good luck with your project. Be careful with global state!
Last edited by kikito on Thu Sep 11, 2014 9:05 am, edited 1 time in total.
When I write def I mean function.
User avatar
Inny
Party member
Posts: 652
Joined: Fri Jan 30, 2009 3:41 am
Location: New York

Re: Help me design a Singleton Mixin for middleclass?

Post by Inny »

Lua is functional enough where you could just express this as a lazy instantiation function and pass that around.

Code: Select all

function lazy(theClass)
    local instance
    return function()
        if not instance then instance = theClass:new() end
        return instance
    end
end

-- Make it a global variable
SomeImportantClass.static.getInstance = lazy(SomeImportantClass)

-- And pass it around as needed
local childObject = SomeOtherClass:new(SomeImportantClass.getInstance)

Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Bing [Bot], Google [Bot] and 74 guests