單看名稱就很好理解的設計模式,就是只能有一個且是唯一實體的物件。有些時候最好讓物件只能有一個以避免程式出錯,例如負責處理使用者登入的物件,假如不是獨體模式的話,登入物件就可能有多個,使用者就可能同時登入很多次。
以上就是最基本的獨體模式,接下來來看一下正式定義及類別圖:
設計此模式的想法也很簡單,不要讓別人能用 new 來建立物件就好,也就是建構子不能宣告為 public。因為其他人不能使用 private 建構子,只能在類別內使用…說那麼多,直接看程式碼比較快:
public class Singleton { // 利用一個靜態變數記綠 Singleton 的實體 private static Singleton sInstance; // 可以宣告其他需要的成員變數 // 建構式宣告為 private, 這樣只有在 // Singleton 類別內才能使用 private Singleton(){} // 公開的靜態方法, 其他人要取得物件只能使用此方法 public static Singleton getInstance() { // 不為 null, 表示之前曾建立過, 不用再 new 一次 if(sInstance == null) { // 需要時才建立物件, 稱為 lazy instantiaze sInstance = new Singleton(); } return sInstance; } }
獨體模式確保一個類別只有一個實體,並給它一個存取的全域點(global point)
Ensure a class has only one instance and provide a global point of access to it.
Ensure a class has only one instance and provide a global point of access to it.
定義及實作上看起來雖然簡單,但在實務上可能會遇到問題。以上面的程式碼來看,是否真的只會有一個且唯一的物件呢 ? 以多執行緒的角度來看就可以知道,是有可能產生多個物件的,解決方法也不難,在 getInstance() 加上 synchronized 就可以了:
public static synchronized Singleton getInstance() { if(sInstance == null) { sInstance = new Singleton(); } return sInstance; }這樣做看起來不錯,但是要考慮效能問題。因為只有第一次要 new 物件時才需要同步,這樣寫的話,每次要取得物件都要同步。因此可以改成以下方法:
public class Singleton { // 在靜態初始化方法(static initializer)中建立物件 // 保證 thread safe private static Singleton sInstance = new Singleton(); private Singleton(){} // 已經有實體了, 可以直接回傳 public static Singleton getInstance() { return sInstance; } }上面程式碼是例用 JVM 在載入此類別時,就馬上建立唯一的獨體物件。JVM 保證在任何執行緒存取 sInstance 之前會先建立物件。但假如在建立物件時需要比較大的 loading 時,此方法也不適用,因此還可以改為使用「雙重檢查上鎖」(double-checked locking) 的方式:
public class Singleton { // 利用 volatile, 可以保證此變數的值是一致的 private volatile static Singleton sInstance; private Singleton(){} public static Singleton getInstance() { // 只有第一次建立物件才會完整執行此段程式 if(sInstance == null) { synchronized (Singleton.class) { // 進入同步區後再檢查一次, // 還是 null 才建立實體 if(sInstance == null) { sInstance = new Singleton(); } } } return sInstance; } }雙重檢查上鎖只有要注意需要在 jdk 1.5 版以上才能用,1.4 以前的版本,對於 volatile 的實作會讓此方法失效
參考資料:
深入淺出設計模式(Head First Design Patterns)
深入淺出設計模式(Head First Design Patterns)
留言
張貼留言