Singletons and how to use them

I believe it’s customary for every coding blog to have a rant about singletons, and this one is no different. It is called coderambling after all.

I’m not here to tell you how to live your life, if you want to use singletons feel free to do so. But I also believe it’s important to know what you’re getting into so you can make an informed decision.

When it comes to using singletons I firmly believe that you shouldn’t. Honestly this is pretty much the whole article. But I suppose some justification is in order.

Why are singletons considered bad?

  • Global state

Singletons are basically a global variable. They can be magically used anywhere due to their static nature. Any change in one place is propagated in the entire system, this makes debugging very hard.

And since no global variable comes alone you can be sure that whatever other classes the singleton uses will also be global, because the singleton makes them that way.

  • Hides dependencies

Every singleton is a hidden dependency. Due to the way they’re used it’s not immediately obvious that a singleton exists somewhere inside your class/method.

When you add a singleton to your code you immediately create a hard, hidden dependency to that singleton.

  • Hard to test

Using a singleton in your system automatically means testing is going to be unnecessarily difficult since you have a hard dependency on a class, which can’t be easily removed.

Depending on how the singleton is built and what it does it may be essentially impossible to test whatever class uses it.

Imagine having a singleton that connects to some remote server to do stuff. Unit testing goes out the window immediately at worse and at best it becomes difficult.

What is the alternative?

The idea behind a singleton has very real world uses. The idea being that “I require only one instance of something”.

Lets take a look at some code.

 class Logger
{
    private static ?Logger $instance = null;
    private function __construct() {}

    public static function getInstance() : Logger
    {
        if(null != self::$instance) {
            return self::$instance;
        }

        self::$instance = new self();

        return self::$instance;
    }

    public function log(string $message) : void
    {
        // do logging here
    }
}

And lets use the singleton logger in a class.

class SomeClass
{
    public function doStuff() {
        // do some computation
        Logger::getInstance()->log("message");
    }
}

You’ll immediately notice that SomeClass has a hard, hidden dependency on Logger. Obviously this is not great.

The alternative is to figure out what the “top” of your system is. Create that one instance there and pass it around.

Ideally you’d use an interface along with dependency injection to do that. If you also use a Dependency Injection Container then it will automatically make your job easier.

This will give you effectively the same benefits of a singleton .

Here’s the above code but rewritten to support DI.

interface Logger
{
    public function log(string $message) : void;
}

class LoggerImpl implements Logger {
    public function log(string $message) : void
    {
        // do logging here
    }
}

class SomeOtherClass
{
    public function __construct(private readonly Logger $logger){}

    public function doStuff() : void
    {
        // do some computation of sorts
        $this->logger->log("some message");
    }
}

Almost all of the above disadvantages have disappeared:

  • The dependencies are clear, nothing is hidden
  • The code can easily be tested by mocking the interface

They logger is still essentially global but in this case that’s fine! If you were to manually create the logger class at the “top” of your system you’ll be passing around the same class.

But what if you really like singletons?

Above I said that having a logger as a global is fine, but why? I also said that globals are bad. So which is it?

For me to use a singleton in my projects they must always conform to at least one of the following

  • They are immutable or work with immutable objects
  • The system doesn’t change behavior if a singleton is removed

The best example of the second one is logging. With our without logging your system will work exactly the same since the flow of information is always out.

However…

Doing what I did above can very quickly lead to constructor cluttering with many parameters. Logging is so common that unless whoever does it uses aspect orientate programming(which isn’t very popular in PHP) you’ll have to pass around the logger to almost every class in your system. Which is just not feasible.

So yes, having a singleton logger can work but I’m still not a huge fan of it. Even so, it must be built carefully since it can interfere with testing.

Another legitimate example is having a service locator as singleton. You’d simply pass that around in your constructor and the service locator would know how to access everything else. This isn’t very commonly used but it is a legitimate case.

A factory is another use case(which I suppose is covered by the locator). Yes, removing the factory would change the behavior since you’d be removing the abstraction layer between the caller and new instances of whatever objects the factory creates, but nothing is stopping you creating the objects using new. So the behavior itself is the same.

Conclusion

Try to avoid singletons whenever you can. There are very few legitimate uses for them.

If you need a single instance of something then create it at the top of your system and pass it around via dependency injection. A DI Container can help with that.

The issue isn’t that you want a single instance of something the issue is the global state that a singleton provides. All singletons provide global state, that’s simply built into them, there’s no way around it.

If you really really want a singleton use one that doesn’t change the state of your system. Ideally an immutable one, those are fine.

Using them is a slippery slope. You need a lot of discipline to keep them in check.


Posted

in

,

by