Saturday, August 25, 2012

Singletons: The Good, The Bad, and the Ugly


Simply put, Singletons are classes for which there is a finite number of instantiations allowed throughout the application.  In it's simplest form it looks something like the factory pattern.
class Singleton{
  private Singleton(){}
  private static Singleton _instance;

  public static getInstance(){
    return (_instance = _instance ?? new Singleton());
  }
}

And to see a finite number of instantiations greater than one.
class SingletonIsAMisnomer{
  private SingletonIsAMisnomer() {}
  private static List<SingletonIsAMisnomer> _instances;
  private static int numberOfInstances = 42;

  public static getInstance(){
    if(_instances.Length < numberOfInstances){
      var newSingletonIsAMisnomer = new SingletonIsAMisnomer();
      _instances.add(newSingletonIsAMisnomer);
      return newSingletonIsAMisnomer;
    } else {
      return _instances[numberOfInstances - 1];
    }
  }
}

This example was is a bit silly to keep the concept clear.  Often times this sort of singleton will be seen in resource pools.

The smells associated with the need of the singleton pattern are any of the following: objects are demanding of resources to instantiate, a finite shared resource across the system (e.g. database access, peripherals, threads, etc), or application state variables floating about in a global or semi-global state.  All of these constitute a situation where a Singleton would help simplify design.

There is some overlap between singletons and static classes.  Static classes also ensure that a finite number of instantiations are floating around in an application, but they also ensure that the finite number is one.  Static classes also do not allow lazy initialization, so if the class is resource heavy to get up and running you're forced to pay upfront for it, without a real good way to dispose of it.  Singletons overcome these limitations of static classes but are still leveraging static instances accessed by static methods, which are dangerously close to simply using global variables.

It's my opinion that singleton classes should default to being sealed.  The reason for this is any inheriting class coming along now must implement the singleton pattern as well or else the singleton pattern is simply not in use anymore, calling 'new' on the inheriting class implicitly instantiates the base class (remember, inheritance => IS-A, 'new inheritingClass' IS-A 'new inheritedClass'). This breaks the concepts of encapsulation and DRY, now when inheriting a singleton one must know that it is a singleton and implement the same pattern.  Static classes do not require this caution as the compiler forces the inheriting class to be static and that's all there is to it.

Both Singletons and static classes can be a form of global variable if used incorrectly.  If all throughout an application there are calls to 'Singleton.GetInstance()', which then operate on or branch off of the Singleton, a change in one location can cause changes in a non-obvious way to a distant location.  This is the danger of global variables, this long distance and nearly invisible coupling.  Keeping calls to 'Singleton.GetInstance()' to a minimum and passing around the instance variable instead prevents this hazard.  While it may increase the size of parameter lists it can greatly simplify code.  Simple code is much easier to fix  and much more difficult to get wrong in the first place.

It is very important to understand that the Singleton pattern can at times expose one to shared resource problems.  If there is only a finite number of instances, it is possible that multiple threads could cause a race condition on those resources.  The hamfisted solution to the problem is to simple introduce locking to the getInstance method, however this causes a lot of unnecessary locking if an instance is already created.  The double-checked locking pattern solves this much more elegantly by only locking when a new instance is needed to be created (it checks for the need to create a new instance, acquires the lock, checks for the need to create a new instance again, and creates the new instance if there is still a need, then releases the lock).  This is sufficient as long as the singleton itself is not mutable.

Singletons with mutable state should be considered an anti-pattern as they produce coupling the same way that global variables do as well as increase complexity in a multi-threaded environment.  Often times the baby will be thrown out with the bath water and all of the Singleton pattern will be declared an anti-pattern because of how detrimental this can be.

No comments:

Post a Comment