單例模式動機
對于系統(tǒng)中某些類來說,只有一個實例是很重要的,例如,一個系統(tǒng)中可以存在多個打印任務(wù),但只能有一個正在工作的任務(wù);一個系統(tǒng)只能有一個窗口管理器或文件系統(tǒng)。因此有時確保系統(tǒng)中某個對象的唯一性即一個類只能有一個實例非常重要。
如何保證一個類只有一個實例并且這個實例易于被訪問呢?定義一個全局變量可以確保對象隨時都可以被訪問,但不能防止我們實例化多個對象。一個更好的解決辦法就是讓類自身負(fù)責(zé)保存它的唯一實例。這個類可以保證沒有其他實例被創(chuàng)建,并且它提供一個訪問該實例的方法。這就是單例模式的動機。
單例模式定義
單例模式確保某一個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例,這個類成為單例類,它提供全局訪問的方法。
單例模式的要點有三個:一是某個類只能有一個實例;二是它必須自行創(chuàng)建這個實例;三是它必須自行向整個系統(tǒng)提供這個實例。
單例模式是一種對象創(chuàng)建模式,又名單件模式或單態(tài)模式。

單例模式只包含一個Singleton(單例角色):在單例類的內(nèi)部實現(xiàn)只生成一個實例,同時它提供一個靜態(tài)的getInstance()工廠方法,讓客戶可以使用它的唯一實例;為了防止在外部對其實例化,將其構(gòu)造函數(shù)設(shè)計為似有;在單例類內(nèi)部定義了一個Singletong類型的靜態(tài)對象,作為外部共享的唯一實例。
模式分析
單例模式的目的是保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。
public class Singleton {
private static Singleton instance=null;//靜態(tài)私有成員變量
//私有構(gòu)造函數(shù)
private Singleton(){}
//靜態(tài)工廠方法,返回唯一實例
public static Singleton getInstance(){
if(instance==null)
instance=new Singleton();
return instance;
}
}
為了測試單例類所創(chuàng)建對象的唯一性,可以編寫如下客戶端測試代碼:
public class ClientTest {
public static void main(String args[]){
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
System.out.println(s1==s2);
}
}
輸出結(jié)果:
true
在單例模式的實現(xiàn)過程中,需要注意一下三點:
1.單例類的構(gòu)造模式為私有
2.提供一個自身的靜態(tài)私有成員變量
3.提供一個公有的靜態(tài)工廠方法
單例模式實例與解析
1.實例說明
在現(xiàn)實生活中,居民身份證具有唯一性,同一個人不允許有多個身份證號碼,第一次申請身份證將給居民分配一個身份證號碼,如果之后因為遺失等原因補辦時,還是使用原來的身份證號碼,不會產(chǎn)生新的號碼。
2.實例類圖

3.實例代碼及解釋
單例類IdentityCardNo如下:
public class IdentityCardNo {
private static IdentityCardNo instance=null;
private String no;
private IdentityCardNo(){}
public static IdentityCardNo getInstance(){
if(instance==null){
System.out.println("第一次辦理身份證,分配新號碼");
instance=new IdentityCardNo();
instance.setIdentityCardNo("No123456789");
}else{
System.out.println("重復(fù)辦理身份證,獲取舊號碼");
}
return instance;
}
private void setIdentityCardNo(String no) {
this.no=no;
}
private void getIdentityCardNo() {
this.no=no;
}
}
客戶端代碼如下:
public class ClientTest {
public static void main(String a[]){
IdentityCardNo no1,no2;
no1=IdentityCardNo.getInstance();
no2=IdentityCardNo.getInstance();
System.out.println("身份證號碼是否一致: "+ (no1==no2));
String str1,str2;
str1=no1.getIdentityCardNo();
str2=no2.getIdentityCardNo();
System.out.println("第一次號碼:"+str1);
System.out.println("第二次號碼:"+str2);
System.out.println("內(nèi)容是否相等:"+str1.equals(str2));
System.out.println("是否是相同對象:"+(str1==str2));
}
}
運行結(jié)果如下:
第一次辦理身份證,分配新號碼
重復(fù)辦理身份證,獲取舊號碼
身份證號碼是否一致: true
第一次號碼:No123456789
第二次號碼:No123456789
內(nèi)容是否相等:true
是否是相同對象:true
從結(jié)果可以看出,兩次創(chuàng)建的IdentityCardNo對象內(nèi)存地址相同,是同一個對象;封裝在其中的號碼no屬性不僅值相等,而且內(nèi)存地址也相等,是同一個成員屬性。
單例模式優(yōu)缺點
優(yōu)點:
(1)提供了 對唯一實例的受控訪問。因為單例類封裝了它的唯一實例,所以它可以嚴(yán)格控制客戶怎樣以及何時訪問它。
(2)由于在系統(tǒng)中只存在一個對象,因此可以節(jié)約系統(tǒng)資源,對于一些需要頻繁創(chuàng)建和銷毀的對象,單例模式無疑可以提高系統(tǒng)的性能
(3)允許可變數(shù)目的實例。基于單例模式我們可以進(jìn)行擴展,使用與單例控制相似的方法來獲得指定個數(shù)的對象實例。
缺點:
(1)由于單例模式?jīng)]有抽象層,因此單例類的擴展有很大的困難
(2)單例類的職責(zé)過重,在一定程度上違背了“單一職責(zé)原則”。因為單例類既充當(dāng)了工廠角色,提供了工廠方法,同時又充當(dāng)了產(chǎn)品角色,包含一些業(yè)務(wù)方法,將產(chǎn)品的創(chuàng)建和產(chǎn)品本身的功能融合到一起。
(3)濫用單例將帶來一些負(fù)面問題,如為了節(jié)省資源將數(shù)據(jù)庫連接池對象設(shè)計成單例類,可能導(dǎo)致共享連接池對象的程序過多而出現(xiàn)連接池溢出。等。
模式適用環(huán)境
1.系統(tǒng)只需要一個實例對象,如系統(tǒng)要求提供一個唯一的序列號生成器,或者需要考慮資源消耗太大而只允許創(chuàng)建一個對象。
2.客戶調(diào)用類的單個實例只允許使用一個公共訪問點,除了該公共訪問點,不能通過其他途徑訪問該實例。
單例模式擴展
(1)餓漢式單例類:在定義靜態(tài)變量時實例化單例類,因此在類加載的時候就已經(jīng)創(chuàng)建了單例對象。
public class EagerSingleton {
private static final EagerSingleton instance=null;
private EagerSingleton(){}
public static EagerSingleton getInstance(){
return instance;
}
}
在這個類被加載時,靜態(tài)變量instance會被初始化,此時類的私有構(gòu)造函數(shù)會被調(diào)用,單例類的唯一實例將被創(chuàng)建。Java語言中單例類的一個重要的特點是類的構(gòu)造函數(shù)是私有的,從而避免外界利用構(gòu)造函數(shù)直接創(chuàng)建出任意多的實例。
(2)懶漢式單例類:與餓漢式單例類相同之處是,懶漢式單例類的構(gòu)造函數(shù)也是私有的。與餓漢式單例類不同的是,懶漢式單例類在第一次被引用時將自己實例化,在懶漢式單例類被加載時不會講自己實例化。
public class LazySingleton {
private static LazySingleton instance=null;
private LazySingleton(){}
synchronized public static LazySingleton getInstance(){
if(instance==null){
instance=new LazySingleton();
}
return instance;
}
}