7 minutes reading time (1364 words)

Why the Poor Old Singleton was Banned

Why the Poor Old Singleton was Banned

The once very popular Singleton design pattern has become an anti-pattern. From hero to outcast. Like a sportsman who was caught doping. What happened?

Let me introduce my old friend the Singleton. When learning something about design patterns, he  is one of the first you meet. The intent of a Singleton, as defined in the classical 'Gang of Four' book about design patterns (1994):

"ensure a class only has one instance, and provide a global point of access to it."

This is mostly done by making the constructor of a class, and other instantiating methods like __clone(),  private and final, while providing a static method to instantiate the class. The class holds a static reference to the instance and every time the getInstance() method is called, the class is only instantiated when no instance already exists. So, you cannot call new YourSingletonClass , because the constructor is private. And when you call YourSingletonClass::getInstance() , a new object is only made when there is not one around already.

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

a getInstance() method of a Singleton

Simple, handy and clever, isn't it?

Love

We all loved the Singleton and he was used at many places. A well known example is: when you use a database connection in your application, you want to make sure, you won't end up with several connections to the same database in that application. But slowly we began to realise what the downside of this handy piece of code is: we often use it as a way to get a global variable and that realy has become a dirty word nowadays. Our poor Singleton was unmasked as a global in disguise.

Spaghetti

I like Italian food. Spaghetti is a brilliant way of preserving and eating wheat. I love it on my plate, but not in my IDE. When pieces of code are tightly coupled, maintenance becomes a nightmare: you change something at one spot and suddenly you'll have to change other parts elsewhere as well. Code should not be like spaghetti. So we try to make small building blocks, that are as independant as possible from each other. You can then change one piece and all the rest stays the same. Such independant units of software can also be tested: all input into a unit should be under control, for instance by using temporary replacements (mocks). If any value in your software changes beyond the control of a test, you cannot be sure the test is right or wrong. When your software under test uses objects that are also used elsewhere, you cannot test it. Hence you'll have to replace it with a mock that mimicks the behaviour of that external object, assuring it will not change during the test. A global cannot be mocked and so a piece of software with a global in it cannot be easily tested. The dependance should be made more explicit. This can be done by injecting the dependency into your code, for instance as a parameter in a class-constructor. It not only makes your software testable, but in general decouples pieces of code. Software should have high cohesion but low coupling in order to improve maintanability.

Global in disguise

The problem with singletons is that they are often used as globals. Wherever you could replace $myObject = YourSingletonClass::getInstance() by global $myObject you are in fact doing the same thing with just a different syntax. It yields the same problems. Therefore the Singleton often is a code smell, an anti-pattern. Dependency injection can give a solution in many cases. By the way: you don't need a Dependency Injection Container to use dependency injection, but that is for another article.

Human

Most software developers have human aspects. For instance: it is very tempting to do something the easy way, although you know it can have negative consequences in the long run. Globals are very easy to use, but if obvious ways to use them are not accepted anymore, we sometimes use more stealth ways of accomplishing the same. Some implementations of Registries, Service Locators and our good old Singleton are examples of that. Another human aspect you can find under developers is to absolutise moral generalisations of "good" and "bad". Once the Singleton was labled as "bad", there was nothing "good" to it anymore: it has a static (bad!) getInstance() method and having responsiblity for its own uniqueness is violating (bad!) the Single Responsibility Principle. I see that kind of behaviour, to label something as absolutely good or bad, as a way to train ourselves in doing things a specific way.

Instantiating a specific class only once in some piece of software can be useful. A Repository pattern is doing that job too, for instance. The Singleton is just not the optimal way to use a single instance of a class in different parts of your software. That is a global in disguise. With dependency injection you can accomplish the same, while keeping your software better decoupled and testable. There is no need to use a Singleton: if you only want one instance of a class, then only instantiate it once. If you need that object on several places, then get it there. If that makes you push around large amounts of objects, then something is wrong with your architecture.

Joomla!

The Joomla! CMS uses a Singleton pattern for its database connection. In fact it uses a brother of the Singleton, called Multiton, which is a collection of Singletons: only a single DatabaseDriver with a specific signature (a specific set of options)  can be instantiated.

public static function getInstance($options = array())
{
    // Sanitize the database connector options.
    // (...)

    // Get the options signature for the database connector.     $signature = md5(serialize($options));
    // If we already have a database connector instance for these options then just use that.     if (empty(self::$instances[$signature]))     {
        // Derive the class name from the driver.         $class = 'JDatabaseDriver' . ucfirst(strtolower($options['driver']));
        // If the class still doesn't exist we have nothing left to do but throw an exception. We did our best.         if (!class_exists($class))         {             throw new RuntimeException(sprintf('Unable to load Database Driver: %s', $options['driver']));         }
        // Create our new JDatabaseDriver connector based on the options given.         try         {             $instance = new $class($options);         }         catch (RuntimeException $e)         {             throw new RuntimeException(sprintf('Unable to connect to the Database: %s', $e->getMessage()));         }
        // Set the new connector to the global instances based on signature.         self::$instances[$signature] = $instance;     }
    return self::$instances[$signature]; }

the getInstance() method of JDatabaseDriver

 

If we would always pass that database connection to an object that needs it, then the dependency would be clear. Now it is hidden. Most of the time we produce the database connection on the spot where we need it. That is an example of using a singleton as a global.

You can give a database object to the Model in the $config, but if you don't, then one is instantiated:

public function __construct($config = array())
{
    // (...)
    // Set the model dbo
    if (array_key_exists('dbo', $config))
    {
        $this->_db = $config['dbo'];
    }
    else
    {
        $this->_db = JFactory::getDbo();
    }
    // (...)
}

part of the constructor of JModelLegacy

 

Two years ago some basic classes were built for a new MVC. You can find them in the Joomla Framework. When you look at the Joomla\Model\AbstracDatabaseModel class you see a $db parameter in its constructor. That is an example of dependency injection: the dependency (upon the database connection) is injected into the Model-object:

public function __construct(DatabaseDriver $db, Registry $state = null)
{
    $this->db = $db;
 
    parent::__construct($state);
}

the constructor of Joomla\Model\AbstracDatabaseModel

That new DatabaseModel was intended to be used everywhere where we need a database connection. No need for Helper classes with their static methods (another code smell), as are used now in modules in the Joomla CMS. If the database connection is always injected in the model, we don't need a Singleton anymore. Choosing between different drivers can be done by other, more suitable design patterns like Builder, Bridge or Strategy. Because the Joomla Framework doesn't have to be backwards compatible, the getInstance() implementation of the Singleton can be removed from Joomla\Database\DatabaseDriver.

Some references

 

0
Utiliser SobiPro en tant que CCK
 

Comments

Already Registered? Login Here
No comments made yet. Be the first to submit a comment

By accepting you will be accessing a service provided by a third-party external to https://magazine.joomla.org/