1. 程式人生 > >用私有構造器或者列舉型別強化Singleton屬性-03

用私有構造器或者列舉型別強化Singleton屬性-03

術語:

Singleton:指僅僅被例項化一次的類。

        Singleton會使它的客戶端測試變得十分困難,因為無法給Singleton替換模擬實現,除非它實現一個充當其型別的介面。

        實現Singleton有以下三種方法:

1、實現公有靜態成員函式,並將之設定為final。例如

  1. // Singleton with public field
  2. public class Elvis {
  3. public static final Elvis INSTANCE = new Elvis();
  4. private Elvis() { ... }
  5. public void
    leaveTheBuilding()
    { ... }
  6. }
        這種方法把建構函式私有化,這樣保證了Elvis的全域性唯一性。但有一個問題,就是享有特權的客戶端可能借助AccessibleObject.setAccessible方法,通過反射機制呼叫私有構造器,這樣就破壞了Singleton,一種補救的辦法是,修改構造器讓他被要求生成第二個例項的時候丟擲異常。

2、例項私有化,但通過公有的靜態方法返回,例如

  1. public class Elvis {
  2. private static final Elvis INSTANCE = new Elvis();
  3. private Elvis
    ()
    { ... }
  4. public static Elvis getInstance() { return INSTANCE; }
  5. public void leaveTheBuilding() { ... }
  6. }
        對於靜態方法Elvis.getInstance的所有呼叫都會返回同一個物件的引用,這樣就保證了唯一性,但是還是存在第1種方法裡的反射機制進行攻擊的問題。

        第一種方法的好處在於組成類的成員的宣告很清楚的表明了這是類的一個Singleton,但是相比之下不再比第二種方法有什麼優勢,因為JVM幾乎都將靜態工廠方法的呼叫內聯化。第二種方法的優勢之一在於,它提供了靈活性,在不改變其API的前提下,可以改變該類是否應該是Singleton的想法,這種方法可以很容易的被修改成按不同需求返回相應的唯一例項。第二個優勢在於與泛型有關,但是相比之下,第一種方法更為簡單。

        以上兩種方法還有一個問題,就是在實現Singleton類序列化的時候,僅僅在聲名中加上“implement Serializable”是不夠的。為了維護並保證Singleton,必須聲名所有的例項都是瞬時的(transient),並提供一個readResolve方法。否則的話每次反序列化的時候都會建立一個新的例項,這個時候要加入以下程式碼

  1. // readResolve method to preserve singleton property
  2. private Object readResolve() {
  3. // Return the one true Elvis and let the garbage collector
  4. // take care of the Elvis impersonator
  5. return INSTANCE;
  6. }
3、包含單個元素的列舉型別。
  1. public enum Elvis {
  2. INSTANCE;
  3. public void leaveTheBuilding() { ... }
  4. }
        這種方法更加簡潔,無償的提供了序列化機制,絕對防止多次例項化(由Enum保證),即使是在面對複雜的序列化或者反序列化或者反射攻擊的時候也可以保證唯一。這已成為實現 Singleton的最佳方法。