**寫在前邊:**辛辛苦苦寫了好幾天終于能有一篇發到首頁上了,其中的艱辛就不必多說了,我不是專家不發能首頁,好多文章博樂也都不看,比起首頁上那些空洞無味的文章,我覺得我的博客對一部分人能起到幫助的作用,如果您覺得我寫的還可以就頂一下吧,您的支持是我最大的動力!
### 一、模式定義
單例模式(Singleton Pattern):單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統提供這個實例,這個類稱為單例類,它
提供全局訪問的方法。
單例模式的要點有三個:一是某個類只能有一個實例;二是它必須自行創建這個實例;三是它必須自行向整個系統提供這個實例。單例模式
是一種對象創建型模式。單例模式又名單件模式或單態模式。
### 二、模式結構

我們可以看到單例模式與我們之前所講的工廠模式不同,它只有一個Singleton角色,并且這個類自行創建自己的實例,并向系統提供這個實
例,可以注意到我沒有在圖中吧實例的建造個數限制為1個,這一點我們后邊會講到多例模式。
單例模式分為懶漢式的和餓漢式,有的地方也會講登記式的單例模式,下面我們就一起來學習一下這三種單例模式。
### 三、餓漢式的單例模式
餓漢式單例模式是Java語言中實現起來最為簡單的單例模式,我們通過代碼來看一下。
~~~
package com.designpattern.singleton;
public class EagerSingleton {
/**
* 直接創建一個本類的對象
*/
private static final EagerSingleton eagerSingleton = new EagerSingleton();
/**
* 覆蓋默認的構造方法,將默認的構造方法聲明為私有的,防止其他類直接創建對象
*/
private EagerSingleton(){}
/**
* 提供一個工廠方法來返回本類的唯一對象
* @return
*/
public static EagerSingleton getInstance(){
return eagerSingleton;
}
}
~~~
餓漢式單例模式就是一開始就自己創建一個私有的靜態的本類對象,當這個類被加載時,靜態變量eagerSingleton就會被初始化,這時這個
類的私有構造方法就會被調用。這個時候,單例類的唯一實例就被創建出來了。但需要獲得單例類的對象是就調用getInstance方法。
需要注意的一點就是單例類的構造方法一定要聲明為私有的,否則其他類就可以利用構造方法直接創建對象,使單例類不再是只有一個唯一
的實例。
另外,值得一提的是,由于構造方法是私有的,因此此類不能被繼承。
餓漢式單例模式在自己加載時就將自己實例化,所以從資源利用效率的角度來講,餓漢式單例模式不如懶漢式單例模式節省資源,但是餓漢
式單例模式的速度更快一些。
### 四、懶漢式的單例模式
懶漢式單例模式與餓漢式稍有不同,下面看一下代碼:
~~~
package com.designpattern.singleton;
public class LazySingleton {
/**
* 單例類的唯一實例,但是不是加載時初始化
*/
private static LazySingleton lazySingleton = null;
/**
* 覆蓋原有的默認構造方法,聲明為私有的,防止其他類直接使用構造方法創建單例類對象,同時也使子類無法繼承
*/
private LazySingleton() {
}
/**
* 線程互斥的獲取實例
* @return
*/
public static synchronized LazySingleton getInstance() {
/**
* 如果實例為空就創建實例,創建的語句只會執行一次
*/
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
~~~
從代碼我們可以看出,懶漢式單例模式的創建對象是在第一次企圖獲得單例類的對象時。另外我們再getInstance方法中使用了
synchronization關鍵字,這是防止出現race condition,使兩個線程new出來兩個單例類的實例。構造方法同樣是私有的,這也決定了子類
不能繼承自這個類。
懶漢式單例模式與餓漢式單例模式相比,更節省資源,但是必須處理好在多個線程同時首次引用此類時的訪問限制問題,特別是當單例類作
為資源控制器在實例化時必然涉及資源初始化,而資源初始化很有可能耗費時間,這就意味著出現多個線程同時首次引用此類的幾率變得較
大。
### 五、登記式的單例模式
為了克服餓漢式單例模式以及懶漢式單例模式類均不可繼承的缺點,產生了一種新的模式——登記式單例模式。
登記式的單例模式中父類中有一個集合,用來存儲所有的子類的實例,當一個子類創建時,必須在父類的中登記,也就是把自己的實例加入
到父類的集合中,當其他類想要獲取子類的實例時,就到父類的集合中查找,找到了就返回,如果找不到就創建這個子類的唯一實例。
這是父類的代碼。
~~~
package com.designpattern.singleton;
import java.util.HashMap;
public class RegSingleton {
/**
* 建立一個HashMap來存儲子類的完整類名和子類的實例
*/
private static HashMap registry = new HashMap<String, RegSingleton>();
/**
* 首先將本類的實例加入到HashMap中
*/
static{
RegSingleton x = new RegSingleton();
registry.put(x.getClass().getName(), x);
}
/**
* 構造方法不再是private的了,所以子類可以繼承了
*/
protected RegSingleton(){
}
/**
* 根據子類傳來的類名返回相應的實例
* @param name 想要獲得的類的完整類名
* @return
*/
public static RegSingleton getInstance(String name){
/**
* 提供默認的類
*/
if(name == null){
name = "com.designpattern.singleton.RegSingleton";
}
/**
* 第一次引用這個類時創建類的實例,利用了反射機制
*/
if(registry.get(name) == null){
try{
registry.put(name, Class.forName(name).newInstance());
}catch(Exception e){
e.printStackTrace();
}
}
/**
* 返回子類想要的類的實例
*/
return (RegSingleton)registry.get(name);
}
}
~~~
這是子類的代碼。
~~~
package com.designpattern.singleton;
public class RegSingletonChild extends RegSingleton{
/**
* 構造方法必須是公有的,否則父類無法產生子類的對象
*/
public RegSingletonChild(){}
/**
* 工廠方法,獲取本類的唯一實例,實際上是借助了父類的getInstance方法
* @return
*/
public static RegSingletonChild getInstance(){
return (RegSingletonChild)RegSingleton.getInstance("com.designpattern.singleton.RegSingletonChild");
}
}
~~~
登記式的單例模式解決了懶漢式和餓漢式不能繼承的缺點,但是子類中的構造方法變為了public的,所以其他類可以直接通過構造方法創建
類的實例而不用向父類中登記,這是登記式單例模式最大的缺點。
### 六、什么情況下使用單例模式
- 系統只需要一個實例對象,如系統要求提供一個唯一的序列號生成器,或者需要考慮資源消耗太大而只允許創建一個對象。
- 客戶調用類的單個實例只允許使用一個公共訪問點,除了該公共訪問點,不能通過其他途徑訪問該實例。
- 在一個系統中要求一個類只有一個實例時才應當使用單例模式。反過來,如果一個類可以有幾個實例共存,就需要對單例模式進行改進,
使之成為多例模式
可能有人會將一個系統中需要的“全局”變量放在一個單例類中,這樣做是不對的。首先,單例模式針對的是只能有一個實例的類而不是變
量,其次一個設計得當的系統就不應該有所謂的“全局”變量,這些變量應該放到他們所描述的實體所對應的類中去。將這些變量從他們的
實體類中抽出來,放到一個不相干的單例類中去,使得這些變量產生錯誤的依賴關系和耦合關系。
在資源方面管理方面,單例模式用的更多,比如一臺計算機連接著一臺打印機,那么這個打印機就只有一臺,也只應該有一個實例,試想如
果有兩個打印機的實例,這兩個實例同時操控打印機會出現什么情況,打出來的東西將是亂七八糟的。
所以應該在合適的情況下合理的使用設計模式,而不是一味的追求一個系統利用了多少模式。
### 七、單例模式的優點和缺點
**單例模式的優點:**
- 提供了對唯一實例的受控訪問。因為單例類封裝了它的唯一實例,所以它可以嚴格控制客戶怎樣以及何時訪問它,并為設計及開發團隊提
供了共享的概念。
- 由于在系統內存中只存在一個對象,因此可以節約系統資源,對于一些需要頻繁創建和銷毀的對象,單例模式無疑可以提高系統的性能。
- 允許可變數目的實例。我們可以基于單例模式進行擴展,使用與單例控制相似的方法來獲得指定個數的對象實例。
**單例模式的缺點:**
- 由于單例模式中沒有抽象層,因此單例類的擴展有很大的困難。
- 單例類的職責過重,在一定程度上違背了“單一職責原則”。因為單例類既充當了工廠角色,提供了工廠方法,同時又充當了產品角色,
包含一些業務方法,將產品的創建和產品的本身的功能融合到一起。
- 濫用單例將帶來一些負面問題,如為了節省資源將數據庫連接池對象設計為單例類,可能會導致共享連接池對象的程序過多而出現連接池
溢出;現在很多面向對象語言(如Java、C#)的運行環境都提供了自動垃圾回收的技術,因此,如果實例化的對象長時間不被利用,系統會認
為它是垃圾,會自動銷毀并回收資源,下次利用時又將重新實例化,這將導致對象狀態的丟失。
### 八、模式在JDK中的應用
Java中的Runtime對象就是一個使用單例模式的例子。在每一個Java應用程序里面,都有唯一的一個Runtime對象,通過這個對象應用程序可
以與其運行環境發生相互作用。
Runtime類提供一個靜態工廠方法getRuntime():
`public static Runtime getRuntime();`
通過調用次方法,可以獲得Runtime類唯一的一個實例:
`Runtime rt = Runtime.getRuntime();`
### 九、擴展:多例模式
單例是只有一個類實例,自然多例模式就是有多個類的實例了。下面就以有兩個實例為例講一下。
~~~
package com.designpattern.singleton;
import java.util.Random;
public class Die {
/**
* 兩個類實例
*/
private static Die die1 = new Die();
private static Die die2 = new Die();
/**
* 私有的構造方法
*/
private Die(){}
/**
* 根據用戶使用的標號來決定返回哪一個對象
* @param witchOne
* @return
*/
public static Die getInstance(int witchOne){
if(witchOne == 1)
return die1;
else
return die2;
}
/**
* 使用對象產生隨機數
* @return
*/
public synchronized int dice(){
Random rand = new Random();
return rand.nextInt(6)+1;
}
}
~~~
~~~
package com.designpattern.singleton;
public class Client {
public static void main(String[] args){
Die die1 = Die.getInstance(1);
Die die2 = Die.getInstance(2);
System.out.println(die1.dice());
System.out.println(die2.dice());
}
}
~~~
我們看到多例模式的多例類中有多于一個對象實例,究竟返回哪一個對象取決于程序的業務邏輯。
源碼下載:[http://download.csdn.net/detail/xingjiarong/9297315](http://download.csdn.net/detail/xingjiarong/9297315)
- 前言
- 設計原則(一)&quot;開-閉&quot;原則(OCP)
- 設計原則(二)里氏替換原則(LSP)
- 設計原則(三)組合復用原則
- 設計原則(四)依賴倒置原則(DIP)
- 設計模式(一)簡單工廠模式
- 設計模式(二)工廠方法模式
- 設計模式(三)抽象工廠模式
- 設計模式(四)單例模式
- 設計模式(五)創建者模式(Builder)
- 設計模式(六)原型模式
- 設計模式(七)門面模式(Facade Pattern 外觀模式)
- 設計模式(八)橋梁模式(Bridge)
- 設計模式(九)裝飾模式(Decorator)
- 設計模式(十)適配器模式
- 設計模式(十一)策略模式
- 設計模式(十二)責任鏈模式
- 設計模式之UML(一)類圖以及類間關系(泛化 、實現、依賴、關聯、聚合、組合)
- 設計模式之橋梁模式和策略模式的區別