在什么情況下使用單例模式
使用單例模式的條件
使用單例模式有一個(gè)很重要的必要條件:
在一個(gè)系統(tǒng)要求一個(gè)類只有一個(gè)實(shí)例時(shí)才應(yīng)當(dāng)使用單例模式。反過來說,如果一個(gè)類可以有幾個(gè)實(shí)例共存,那么就沒有必要使用單例類。但是有經(jīng)驗(yàn)的讀者可能會(huì)看到很多不當(dāng)?shù)厥褂脝卫J降睦樱梢娮龅缴厦孢@一點(diǎn)并不容易,下面就是一些這樣的情況。
例子一
問:我的一個(gè)系統(tǒng)需要一些"全程"變量。學(xué)習(xí)了單例模式后,我發(fā)現(xiàn)可以使用一個(gè)單例類盛放所有的"全程"變量。請(qǐng)問這樣做對(duì)嗎? 答:這樣做是違背單例模式的用意的。單例模式只應(yīng)當(dāng)在有真正的"單一實(shí)例"的需求時(shí)才可使用。
一個(gè)設(shè)計(jì)得當(dāng)?shù)南到y(tǒng)不應(yīng)當(dāng)有所謂的"全程"變量,這些變量應(yīng)當(dāng)放到它們所描述的實(shí)體所對(duì)應(yīng)的類中去。將這些變量從它們所描述的實(shí)體類中抽出來, 放到一個(gè)不相干的單例類中去,會(huì)使得這些變量產(chǎn)生錯(cuò)誤的依賴關(guān)系和耦合關(guān)系。
例子二
問:我的一個(gè)系統(tǒng)需要管理與數(shù)據(jù)庫的連接。學(xué)習(xí)了單例模式后,我發(fā)現(xiàn)可以使用一個(gè)單例類包裝一個(gè)Connection 對(duì)象,并在finalize()方法中關(guān)閉這個(gè)Connection 對(duì)象。這樣的話,在這個(gè)單例類的實(shí)例沒有被人引用時(shí),這個(gè)finalize() 對(duì)象就會(huì)被調(diào)用,因此,Connection 對(duì)象就會(huì)被釋放。這多妙啊。
答:這樣做是不恰當(dāng)?shù)摹3怯袉我粚?shí)例的需求,不然不要使用單例模式。在這里Connection 對(duì)象可以同時(shí)有幾個(gè)實(shí)例共存,不需要是單一實(shí)例。
單例模式有很多的錯(cuò)誤使用案例都與此例子相似,它們都是試圖使用單例模式管理共享資源的生命周期,這是不恰當(dāng)?shù)摹?BR> 單例類的狀態(tài)
有狀態(tài)的單例類
一個(gè)單例類可以是有狀態(tài)的(stateful),一個(gè)有狀態(tài)的單例對(duì)象一般也是可變(mutable) 單例對(duì)象。 有狀態(tài)的可變的單例對(duì)象常常當(dāng)做狀態(tài)庫(repositary)使用。比如一個(gè)單例對(duì)象可以持有一個(gè)int 類型的屬性,用來給一個(gè)系統(tǒng)提供一個(gè)數(shù)值惟一的序列號(hào)碼,作為某個(gè)販賣系統(tǒng)的賬單號(hào)碼。當(dāng)然,一個(gè)單例類可以持有一個(gè)聚集,從而允許存儲(chǔ)多個(gè)狀態(tài)。
沒有狀態(tài)的單例類
另一方面,單例類也可以是沒有狀態(tài)的(stateless), 僅用做提供工具性函數(shù)的對(duì)象。既然是為了提供工具性函數(shù),也就沒有必要?jiǎng)?chuàng)建多個(gè)實(shí)例,因此使用單例模式很合適。一個(gè)沒有狀態(tài)的單例類也就是不變(Immutable) 單例類; 關(guān)于不變模式,讀者可以參見本書的"不變(Immutable )模式"一章。
多個(gè)JVM 系統(tǒng)的分散式系統(tǒng)
EJB 容器有能力將一個(gè)EJB 的實(shí)例跨過幾個(gè)JVM 調(diào)用。由于單例對(duì)象不是EJB,因此,單例類局限于某一個(gè)JVM 中。換言之,如果EJB 在跨過JVM 后仍然需要引用同一個(gè)單例類的話,這個(gè)單例類就會(huì)在數(shù)個(gè)JVM 中被實(shí)例化,造成多個(gè)單例對(duì)象的實(shí)例出現(xiàn)。一個(gè)J2EE應(yīng)用系統(tǒng)可能分布在數(shù)個(gè)JVM 中,這時(shí)候不一定需要EJB 就能造成多個(gè)單例類的實(shí)例出現(xiàn)在不同JVM 中的情況。
如果這個(gè)單例類是沒有狀態(tài)的,那么就沒有問題。因?yàn)闆]有狀態(tài)的對(duì)象是沒有區(qū)別的。但是如果這個(gè)單例類是有狀態(tài)的, 那么問題就來了。舉例來說,如果一個(gè)單例對(duì)象可以持有一個(gè)int 類型的屬性,用來給一個(gè)系統(tǒng)提供一個(gè)數(shù)值惟一的序列號(hào)碼,作為某個(gè)販賣系統(tǒng)的賬單號(hào)碼的話,用戶會(huì)看到同一個(gè)號(hào)碼出現(xiàn)好幾次。 在任何使用了EJB、RMI 和JINI 技術(shù)的分散式系統(tǒng)中,應(yīng)當(dāng)避免使用有狀態(tài)的單例模式。
多個(gè)類加載器
同一個(gè)JVM 中會(huì)有多個(gè)類加載器,當(dāng)兩個(gè)類加載器同時(shí)加載同一個(gè)類時(shí),會(huì)出現(xiàn)兩個(gè)實(shí)例。在很多J2EE 服務(wù)器允許同一個(gè)服務(wù)器內(nèi)有幾個(gè)Servlet 引擎時(shí),每一個(gè)引擎都有獨(dú)立的類加載器,經(jīng)有不同的類加載器加載的對(duì)象之間是絕緣的。
比如一個(gè)J2EE 系統(tǒng)所在的J2EE 服務(wù)器中有兩個(gè)Servlet 引擎:一個(gè)作為內(nèi)網(wǎng)給公司的網(wǎng)站管理人員使用;另一個(gè)給公司的外部客戶使用。兩者共享同一個(gè)數(shù)據(jù)庫,兩個(gè)系統(tǒng)都需要調(diào)用同一個(gè)單例類。如果這個(gè)單例類是有狀態(tài)的單例類的話,那么內(nèi)網(wǎng)和外網(wǎng)用戶看到的單例對(duì)象的狀態(tài)就會(huì)不同。除非系統(tǒng)有協(xié)調(diào)機(jī)制,不然在這種情況下應(yīng)當(dāng)盡量避免使用有狀態(tài)的單例類。 |