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)