Fork me on GitHub

Programming Design Notes

Design Pattern - Singleton

| Comments

Singleton Pattern 幫助你創造出唯一一個實例,保證任何物件要使用這個物件時,也是使用相同的實例。 那唯一一個實例有什麼用呢? 可減少系統負擔,不用建立多個實例,尤其是建立實例是需要複雜的計算, 在情況許可下使用 Singleton Pattern 是有助提高系統效能。 或在某些情況下例如 Cache 和 Pool 這些要共用物件的設計,大多數是只有一個實例存在於應用程式。

看看以下例子應該有助你明白這個模式:


公司要你開發一個 Cache 服務,以供應用程式其他服務使用。 一開始你可能設計成這樣:

(Cache.java) download
import java.util.HashMap;
import java.util.Map;

public class Cache {

    private static final Map<String, Object> container = new HashMap<String, Object>();

    public static Object get(String key) {
        return container.get(key);
    }

    public static void put(String key, Object value) {
        container.put(key, value);
    }

}

這個看似沒有什麼問題,但其他物件可以使用 new Cache() 來建立實例,為了解決這個問題, 更改一下 Constructor:

(Cache.java) download
import java.util.HashMap;
import java.util.Map;

public class Cache {

    private static final Map<String, Object> container = new HashMap<String, Object>();

    private Cache() {}

    public static Object get(String key) {
        return container.get(key);
    }

    public static void put(String key, Object value) {
        container.put(key, value);
    }

}

Constructor 改為 private 就沒有其他物件建立到 Cache 實例, 這樣就可以避免了其他物件可以使用 new Cache() 來建立實例這個問題。

亦可以將 Class 改成 abstract 來解決這個問題。

(Cache.java) download
import java.util.HashMap;
import java.util.Map;

public abstract class Cache {

    private static final Map<String, Object> container = new HashMap<String, Object>();

    public static Object get(String key) {
        return container.get(key);
    }

    public static void put(String key, Object value) {
        container.put(key, value);
    }

}

有測試人員測試出建立 java.util.HashMap 是要複雜的計算,又使用了大量的記憶體(假設), 而 Cache 這個物件又不是必須的,希望你可以改為使用到 Cache 時才建立相對應的物件 (Lazy Initialization), 以節省記憶體和 CPU 使用量。

(Cache.java) download
import java.util.HashMap;
import java.util.Map;

public class Cache {

    private final Map<String, Object> container;

    private Cache() {
        container = new HashMap<String, Object>();
    }

    public Object get(String key) {
        return container.get(key);
    }

    public void put(String key, Object value) {
        container.put(key, value);
    }

    public static Cache getInstance() {
        return InstanceHolder.instance;
    }

    private static class InstanceHolder {

        private static final Cache instance = new Cache();

    }

}

你可能會奇怪,在 InstanceHolder 內不是有一句 private static final Cache instance = new Cache(); 嗎。 那裡有 Lazy Initialization? 其實在 JVM 載入 Cache.class 時會檢查有那些 static field 需要初始化, 但這個過程並不包括 static class 亦即是 InstanceHolder。InstanceHolder 只會在執行 Cache.getInstance() 時 被 JVM 載入,JVM 載入 InstanceHolder.class 時亦會檢查有那些 static field 需要初始化, 那時就會將 InstanceHolder.instance 初始化。

更詳細說明可以參考 Wiki: http://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom


公司又下了一個要求,在正確環境中會使用到 java.util.TreeMap實現的 Cache, 而在測試環境中使用 java.util.HashMap 就可以了。

(Cache.java) download
public abstract class Cache {

    private static class InstanceHolder {

        private static Cache instance;
        static {
            try {
                instance = (Cache) cacheImplClass.getMethod("getInstance").invoke(null);
            } catch (Exception e) {
                System.out.println(cacheImplClass + ".getInstance throw exception: " + e.toString());
                System.out.println("Use default cache " + HashMapCache.class);
                instance = HashMapCache.getInstance();
            }
        }

    }

    private static Class<? extends Cache> cacheImplClass;

    public abstract Object get(String key);

    public abstract void put(String key, Object value);

    public static Cache getInstance() {
        return InstanceHolder.instance;
    }

    public final static void setCacheImplClass(Class<? extends Cache> cacheImplClass) {
        if (Cache.cacheImplClass == null)
            Cache.cacheImplClass = cacheImplClass;
        else
          new IllegalArgumentException("Cache implementation class already exist");
    }

}
(HashMapCache.java) download
import java.util.HashMap;
import java.util.Map;

public class HashMapCache extends Cache {

    private final Map<String, Object> container;

    private HashMapCache() {
        container = new HashMap<String, Object>();
    }

    @Override
    public Object get(String key) {
        return container.get(key);
    }

    @Override
    public void put(String key, Object value) {
        container.put(key, value);
    }

    public static Cache getInstance() {
        return InstanceHolder.instance;
    }

    private static class InstanceHolder {

        private static final Cache instance = new HashMapCache();

    }

}
(TreeMapCache.java) download
import java.util.Map;
import java.util.TreeMap;

public class TreeMapCache extends Cache {

  private final Map<String, Object> container;
  
    private TreeMapCache() {
      container = new TreeMap<String, Object>();
    }

    @Override
    public Object get(String key) {
      return container.get(key);
    }

    @Override
    public void put(String key, Object value) {
      container.put(key, value);
    }

    public static Cache getInstance() {
        return InstanceHolder.instance;
    }

    private static class InstanceHolder {

        private static final Cache instance = new TreeMapCache();

    }

}
(Main.java) download
public class Main {

  public static void main(String[] args) {

      Cache.setCacheImplClass(TreeMapCache.class);
      Cache cache = Cache.getInstance();
      Cache cache2 = Cache.getInstance();

      System.out.println(cache);
      System.out.println(cache == cache2);

      cache.put("test", "Object");
      
      System.out.println(cache2.get("test"));

  }

}
執行結果
TreeMapCache@4e82701e
true
Object

暫時只想到這個解決方法,如果你有更好的方法請告訴我。


以下的圖片是描述 Singleton Pattern 的 Class Diagram。(From Wiki)