[8821 views]

[]

[toggle ads]

Singleton Applied

Of course you know the Singleton pattern from the GoF book. But when it comes to implementation you sometimes feel unsure how to properly code one.


When and how to create the instance?

Implicitly with the instance() method
    public final class ConnectionPool {
      private static ConnectionPool instance = null;

      private ConnectionPool() {
        .. do some initialization ..
      }

      public static synchronized ConnectionPool instance() {
         if (instance == null) {
            instance = new ConnectionPool();
         }
         return instance;
      }

      ...
    }
   
- synchronization and if-statement performance penalty to achieve thread-safety
- moderate control over the the initialization time
- re-initialization possible (by adding a reset() method
- strongly discouraged
At class load time
    public final class Cache {
      private static Cache instance = new Cache();

      private Cache() {
      }

      public static Cache instance() {
         return instance;
      }

      ...
    }
   
- exact time when the constructor is called is not well defined, so don't rely on it
- fast and thread-safe
- re-initialization possible (by adding a reset() method)
- seen often and proven useful
Explicitly with a init() method
    public final class ConnectionPool {
      private static ConnectionPool instance = null;

      private ConnectionPool() {
        .. do some initialization ..
      }

      public static void init() {
         instance = new ConnectionPool();
      }

      public static ConnectionPool instance() {
         return instance;
      }

      ...
    }
   
- total control over the initialization time
- fast
- not thread safe during initialization
- re-initialization is possible through subsequent calls to the init() method
- strongly encouraged


static methods vs. instance() indirection

static methods
    public final class ConnectionPool {
      private static ConnectionPool instance = null;
      private Stack connections;
      ...

      public static Connection getConnection() {
         return (Connection) instance.connections.pop();
      }

      public static void returnConnection(Connection conn) {
         instance.connections.push(conn);
      }
    }
   
- less verbous application code: Connection conn = ConnectionPool.getConnection();
- more verbous singleton code: instance.connections.pop()
- more convenient for the user
- The class should be declared final as it makes no sense1 to extend a class that has only static methods. Extending singletons is very questionable design anyway. You are still able to implement any interface though.
instance() indirection
    public class ConnectionPool {
      private static ConnectionPool instance = null;
      private Stack connections;
      ...
      public static Cache instance() {
         return instance;
      }

      public Connection getConnection() {
         return (Connection) connections.pop();
      }

      public void returnConnection(Connection conn) {
         connections.push(conn);
      }
    }
   
- more verbous application code: Connection conn = ConnectionPool.instance().getConnection();
- less verbous singleton code: connections.pop();
- more convenient for the singleton programmer
- It is still possible to extend the class even tough extending singletons is very questionable design.


Monostate

A Monostate object has static member variables only. So all instances of a Monostate class share the same state. This is a really rare pattern and it's use seems quite limited.
not quite a Singleton
      public class  Statistics {
        private static int requestCount = 0;
        
        public Statistics() {
        }
        
        public synchronized int getRequestCount() {
          return requestCount;
        }
        
        public synchronized int countRequest() {
          requestCount++;
        }
      
      }
    
- do not perform initialization in the constructor!
- behaviour very similar to a Singleton
- can be very confusing for the application programmer, so clear documentation is a must.
- can be extended but may loose its monostate character
- be careful about thread safety and possible dead locks


re-initialization and releasing resources

In a singleton you should always provide possibilities to
a) re-initialize the singleton from scratch
b) release all resources held by the singleton
This is essential to be able to set a well-defined fixture for unit tests, software using self-healing mechanisms2, for software that dynamically loads and disposes components, and last but not least for many applications that are supposed to be running 24 hours a day.
Using explicit initialization with a init() method you get re-initialization completely for free.
re-initialization enabled singleton
    public final class ConnectionPool {
      private static ConnectionPool instance = null;
      private Stack connections;

      private ConnectionPool() {
        connections = new Stack();
      }

      public static void init() {
         instance = new ConnectionPool();
      }
      ...
    }
   
- Perform all initialization in the constructor.
- Call init() to re-initialize the singleton but do it in a thread-safe way.
dispose enabled singleton
    public final class ConnectionPool {
      private static ConnectionPool instance = null;
      private Stack connections;

      private ConnectionPool() {
        connections = new Stack();
      }

      public static void init() {
         instance = new ConnectionPool();
      }

      public static void dispose() {
         instance = null;
      }
      ...
    }
   
- In the dispose method throw away the whole instance and not just the state.
- If you only want to clear internal state use re-initialization instead.
- The init() method must be called before you can use the singleton again.

1 Static methods can not be overwritten. A call to a static method is dependent on the type of the identifier and not on the object's class.
2 Self-healing here means to periodically clear internal state and re-initialize parts of an application. This can be used e.g. to handle unspotted memory leaks.