Design patterns - Singleton

From Juneday education
Jump to: navigation, search

Description

In this chapter, we'll discuss a creational design pattern called Singleton. When you want a class to represent an object which is inherently unique such as the Window manager, the file system, the runtime system etc, and want to ensure that there will only ever exist one instance of this class in an application, you can use the Singleton pattern to achieve this. One drawback from using a Singleton, is that its clients (code in classes which use the singleton) are hard to test since it is hard to create a mock object representing the singleton (unless the singleton implements an interface which can be implemented by the mock object).

The basic idea for the solution to this problem of assuring only one instance being created, is to create one instance in the class itself and store it in a static variable, make the constructor private and provide a static method for accessing the only (static) instance.

There are a few ways to implement this, as is shown in the video lecture and presentation.

Classic Singleton

A classic singleton class follows the structure shown below. It uses lazy initialization (the instance is created only when the first call requesting a reference to it arrives). Note that the code focuses on the structure of the class and says nothing about when it is appropriate to use a Singleton or not. Like every design pattern, there are many cases where they are not appropriate and they should only be used when you feel that they really solve the problem at hand.

public class Highlander{
  private static Highlander instance;
  private Highlander(){} // prevent instantiation
  public static Highlander getInstance(){
    if(instance == null){ // only first time this is true
      instance = new Highlander();
    }
    return instance;
  }
  // instance methods
}
// Client code (in main, for instance):
Highlander.getInstance().someMethod();

There are a few examples of Singletons in the Java API, and they include:

  • java.lang.Runtime
  • java.awt.Desktop

Singleton with static initialization

This singleton is initialized on class loading. If having the singleton instance in the memory is not expensive, this is an alternative to lazy initialization.

public class Highlander{
  private static Highlander instance = new Highlander();
  // prevent instantiation
  private Highlander(){}
  // public access! Create the instance on first call
  public static Highlander getInstance(){
    return instance;
  }
  // instance methods...
}
// Client code:
Highlander.getInstance().someMethod();

Using Enum to make a Singleton

Nowadays (since 2005 or so) it is common to use an enum instead. Enums have the advantage of being thread-safe (with singletons implemented lazily as above, there could be a race condition when two threads try to get an instance at the same time and the second thread to reach the getInstance gets tricked by the if-statement and creates a new instance just after the first thread creates its instance).

This is the structure for creating a singleton using an enum:

public enum Highlander{
  INSTANCE;
  // instance methods...
}
//Client code:
Highlander.INSTANCE.someMethod();


// Bonus feature: Enums are thread-safe :)
// This new feature is available since... 2005

Problems / criticism

Some people criticize the Singleton pattern and "Singletons" for being problematic. Here are some common objections:

  • They are generally used as a global instance and in doing so, hide the dependencies of your application in your code, instead of exposing them through the interfaces
  • They violate the Single Responsibility Principle: by virtue of the fact that they control their own creation and life-cycle.
  • They inherently cause code to be tightly coupled and this makes faking them out under test rather difficult in many cases
  • They carry state around for the lifetime of the app which is another hit to testing since you can end up with a situation where tests need to be ordered which is a big no no for unit tests

About testing (in particular, Unit Testing), each unit test should be independent from the other.

If a test uses a Singleton, and it affects the state of the Singleton, the next test will of course be affected by this, leading to test cases which are not independent. A possible solution is to have a single unit test in a single program - which starts with a "fresh" instance of the Singleton, each time.

Source: Johannes Rudolph - blog

More discussions on the problems of Singletons can be found in the links section below.

Chapter videos

English videos

Exercises

Make this class a singleton

public class RootUser{
  private int UID = 0;
  private int GID = 0;
  private String userName = "root";
  private String name = "Super user";
  private String home = "/root";
  private String shell = "/bin/sh";
  public RootUser(){
  }
  @Override
  public String toString(){
    return new StringBuilder(userName)
      .append("(").append(name).append(")")
      .append(" ").append(UID).append(":").append(GID)
      .append(" home: ").append(home).append(" ")
      .append("shell: ").append(shell)
      .toString();
  }
}

Try various versions of the singleton pattern, lazy initialization, static initialization, public static final field, and enum.

Suggested solutions

Expand using link to the right to see the full content.

Proposed solutions

Enum version:

package en;
public enum RootUser{
  INSTANCE;
  private int UID = 0;
  private int GID = 0;
  private String userName = "root";
  private String name = "Super user";
  private String home = "/root";
  private String shell = "/bin/sh";

  @Override
  public String toString(){
    return new StringBuilder(userName)
      .append("(").append(name).append(")")
      .append(" ").append(UID).append(":").append(GID)
      .append(" home: ").append(home).append(" ")
      .append("shell: ").append(shell)
      .toString();
  }
}

Public with lazy init:

package lazy;
public class RootUser{
  private int UID = 0;
  private int GID = 0;
  private String userName = "root";
  private String name = "Super user";
  private String home = "/root";
  private String shell = "/bin/sh";

  private static RootUser instance;

  private RootUser(){
  }

  public static RootUser getInstance(){
    if(instance == null){
      instance = new RootUser();
    }
    return instance;
  }
  
  @Override
  public String toString(){
    return new StringBuilder(userName)
      .append("(").append(name).append(")")
      .append(" ").append(UID).append(":").append(GID)
      .append(" home: ").append(home).append(" ")
      .append("shell: ").append(shell)
      .toString();
  }
}

Public static final INSTANCE variable version:

package pub;
public class RootUser{
  private int UID = 0;
  private int GID = 0;
  private String userName = "root";
  private String name = "Super user";
  private String home = "/root";
  private String shell = "/bin/sh";

  public static final RootUser INSTANCE = new RootUser();

  private RootUser(){
  }
  @Override
  public String toString(){
    return new StringBuilder(userName)
      .append("(").append(name).append(")")
      .append(" ").append(UID).append(":").append(GID)
      .append(" home: ").append(home).append(" ")
      .append("shell: ").append(shell)
      .toString();
  }
}

Test program, to verify that they all work (at least that initialization works):

package main;

public class TestSingletons{
  public static void main(String[] args){
    // Public static INSTANCE:
    System.out.println(pub.RootUser.INSTANCE);
    // enum.INSTANCE:
    System.out.println(en.RootUser.INSTANCE);
    // Classic with lazy init and getInstance():
    System.out.println(lazy.RootUser.getInstance());
  }
}

Chapter links

Source code

Books this chapter is a part of

Further reading

Chapter links

Book TOC | previous chapter | next chapter