本篇會介紹
先簡單的說,這兩種模式都是用於確保某個類別只有一個物件被實體化。而它們的差別在於前這用在單執行緒的應用程式中;後者則用於多執行緒。
GoF 對 singleton 模式如此定義:
(Singleton 模式)保證一個類別僅有一個實體,並提供一個存取它的一個全域存取點。
Singleton 的工作原理如下:
我們曾在 DAY10 談到繳稅規則。當時我們將不同的規則封裝進 Strategy 物件中,而這些物件都是一個 CalcTax
類別的衍生類別的實體化的結果。這代表我們可能會反覆地使用同樣的物件。
由於性能問題,我們希望不要一直反覆地實體化、銷毀這些物件。這時候就可以使用 singleton 模式。以下是 singleton 模式實踐的 Java 程式碼:
class AmericaTax extends Tax {
private static AmericaTax instance;
private AmericaTax() {
// 建構函數私有化
}
public static AmericaTax getInstance() {
if (instance == null) {
instance = new AmericaTax();
}
return instance;
}
}
在本案例中, SalesOrder
會去詢問 Tax
物件應該使用哪種 Tax
物件。在這裡呈現了兩種封裝:
在此我們會用上多型,程式碼如下。
abstract class Tax {
private static Tax instance;
protected Tax() {}
abstract double calcTax(double qty, double price);
public static Tax getInstance() {
// 根據相應規則建立自己的物件
// 此處先單純建立 AmericaTax 物件
instance = AmericaTax.getInstace();
}
public class AmericaTax extends Tax {
private static AmericaTax instance;
private AmericaTax() {}
public static Tax getInstance() {
if (instance == null) {
instance = new AmericaTax();
}
return instance;
}
}
}
以下是這個模式的關鍵特徵。
項目 | 內容 |
---|---|
意圖 | 希望物件只有一個實體。 |
問題 | 幾個不同客戶物件需要參照同一物件,而且希望確保這些類型的物件數目不超過一個。 |
解決方案 | 保證只有一個實體。 |
參與者與協作者 | 客戶物件只能透過 getInstance() 建立 Singleton 實體。 |
效果 | 客戶物件無須操心是否已存在 Singleton 實體。 |
實作 | 1. 增加一個類別的私有靜態成員,參照所需的物件(初值為 null )2, 增加一個公共靜態 method,當成員變數為 null 時實體化該類別,然後返回該成員變數的值3. 將建構函數設為保護或私有,防止其他人能夠直接實體化該類別。 |
以下是 UML 圖:
如同一開始說的,double-checked locking 模式僅適用於多執行緒的應用程式。我們先回頭思考 singleton 模式在多執行緒中,會出現什麼問題。
我們假設一種情境:
instance
是某為 null
。因為第一個執行緒尚未建立(instance
仍是 null
),所以第二個執行緒也建立了一個新實體Singleton
的 new
操作,從而建立了兩個物件假設 Singleton
有狀態,而且我們希望當狀態被改變時,其他物件都應該要注意到,那麼上述的情境就會帶來困擾。
我們可能會想說:只要同步化對 Singleton
物件是否已建立的操作即可。但這樣的同步化會使得所有執行緒都會必須等待關於物件是否存在的檢查。有可能會拖住效能。
另一種想法是把同步化程式碼放進 if(instance == null)
的判斷句後,但其實這也沒有用,因為還是有可能會有建立兩個 Singleton
物件的可能。
這個模式的解決方案會是:當檢查到 null
時(進入判斷句)進行同步,再檢查一次以確保實體尚未建立。此模式的意圖是優化掉不必要的鎖定。Java 的程式碼如下:
class AmericaTax extends Tax {
private static AmericaTax instance;
private AmericaTax() {}
public static Tax getInstance() {
if (instance == null) {
synchronized (this) {
doSync();
}
}
return instance;
}
private synchronized static void doSync() {
if (instance == null) instance = new AmericaTax();
}
}
Double-checked locking 模式的特點如下:
但此模式對 Java 其實並不適用,這原因來自於 Java 編譯器的特性:它本身的優化工作會在構造方法實體化物件前從構造方法返回指向該物件的參照,a.k.a 當 AmericaTax
物件完全構造之前,doSync()
可能就已經完成了。
本書作者提出了一個 Java 適用的解決方案:
class AmericaTax extends Tax {
private static class Instance {
static final Tax instance = new AmericaTax();
}
private static AmericaTax instance;
private AmericaTax() {}
public static Tax getInstance() {
return Instance.instance;
}
}
因為內部類別只會被裝載一次,所以只會建立一個 AmericaTax
物件。
下一篇,我們會介紹 Object pool 模式。先降~明天見!