單例模式


模式動機

    對於系統中的某些類來說,只有一個實例很重要,例如,一個系統中可以存在多個打印任務,但是只能有一個正在工作的任務;一個系統只能有一個窗口管理器或文件系統;一個系統只能有一個計時工具或ID(序號)生成器。
    如何保證一個類只有一個實例並且這個實例易於被訪問呢?定義一個全局變量可以確保對象隨時都可以被訪問,但不能防止我們實例化多個對象。
    一個更好的解決辦法是讓類自身負責保存它的唯一實例。這個類可以保證沒有其他實例被創建,並且它可以提供一個訪問該實例的方法。這就是單例模式的模式動機。

 

模式定義

    單例模式(Singleton Pattern):單例模式確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例,這個類稱為單例類,它提供全局訪問的方法。
    單例模式的要點有三個:一是某個類只能有一個實例;二是它必須自行創建這個實例;三是它必須自行向整個系統提供這個實例。單例模式是一種對象創建型模式。單例模式又名單件模式或單態模式。
 

模式結構

單例模式包含如下角色:
    •  Singleton:單例

模式分析

    單例模式的目的是保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。單例模式包含的角色只有一個,就是單例類——Singleton。單例類擁有一個私有構造函數,確保用戶無法通過new關鍵字直接實例化它。除此之外,該模式中包含一個靜態私有成員變量與靜態公有的工廠方法,該工廠方法負責檢驗實例的存在性並實例化自己,然后存儲在靜態成員變量中,以確保只有一個實例被創建。
單例模式的實現代碼如下所示:
 1 public class Singleton
 2 {
 3     private static Singleton instance=null;  //靜態私有成員變量
 4     //私有構造函數
 5     private Singleton()
 6     {    
 7     }
 8     
 9        //靜態公有工廠方法,返回唯一實例
10     public static Singleton getInstance()
11     {
12         if(instance==null)
13             instance=new Singleton();    
14         return instance;
15     }
16 }
在單例模式的實現過程中,需要注意如下三點:
    • 單例類的構造函數為私有;
    • 提供一個自身的靜態私有成員變量;
    • 提供一個公有的靜態工廠方法。

單例模式實例與解析

實例一:身份證號碼
    • 在現實生活中,居民身份證號碼具有唯一性,同一個人不允許有多個身份證號碼,第一次申請身份證時將給居民分配一個身份證號碼,如果之后因為遺失等原因補辦時,還是使用原來的身份證號碼,不會產生新的號碼。現使用單例模式模擬該場景。
 

實例代碼(JAVA):

 1 public class IdentityCardNo
 2 {
 3     private static IdentityCardNo instance=null;
 4     private String no;
 5     
 6     private IdentityCardNo()
 7     {    
 8     }
 9     
10     public static IdentityCardNo getInstance()
11     {
12         if(instance==null)
13         {
14             System.out.println("第一次辦理身份證,分配新號碼!");
15             instance=new IdentityCardNo();
16             instance.setIdentityCardNo("No400011112222");        
17         }
18         else
19         {
20             System.out.println("重復辦理身份證,獲取舊號碼!");  
21         }
22         return instance;
23     }
24     
25     private void setIdentityCardNo(String no)
26     {
27         this.no=no;
28     }
29     
30     public String getIdentityCardNo()
31     {
32         return this.no;
33     }
34     
35 }
36 
37 public class Client
38 {
39     public static void main(String a[])
40     {
41        IdentityCardNo no1,no2;
42        no1=IdentityCardNo.getInstance();
43        no2=IdentityCardNo.getInstance();
44        System.out.println("身份證號碼是否一致:" + (no1==no2));
45        
46        String str1,str2;
47        str1=no1.getIdentityCardNo();
48        str2=no1.getIdentityCardNo();
49        System.out.println("第一次號碼:" + str1);
50        System.out.println("第二次號碼:" + str2);
51        System.out.println("內容是否相等:" + str1.equalsIgnoreCase(str2));
52        System.out.println("是否是相同對象:" + (str1==str2));
53     }
54 }
實例二:打印池
 
    • 在操作系統中,打印池(Print Spooler)是一個用於管理打印任務的應用程序,通過打印池用戶可以刪除、中止或者改變打印任務的優先級,在一個系統中只允許運行一個打印池對象,如果重復創建打印池則拋出異常。現使用單例模式來模擬實現打印池的設計。
 

實例代碼(JAVA):

 1 public class PrintSpoolerSingleton
 2 {
 3     private static PrintSpoolerSingleton instance=null;
 4     
 5     private PrintSpoolerSingleton()
 6     {    
 7     }
 8     
 9     public static PrintSpoolerSingleton getInstance() throws PrintSpoolerException
10     {
11         if(instance==null)
12         {
13             System.out.println("創建打印池!");
14             instance=new PrintSpoolerSingleton();        
15         }
16         else
17         {
18             throw new PrintSpoolerException("打印池正在工作中!");
19         }
20         return instance;
21     }
22     
23     public void manageJobs()
24     {
25         System.out.println("管理打印任務!");
26     }    
27 }
28 
29 public class PrintSpoolerException extends Exception     
30 {
31     public PrintSpoolerException(String message) 
32     {    
33         super(message);
34     }
35 }
36 
37 public class Client
38 {
39     public static void main(String a[])
40     {
41        PrintSpoolerSingleton ps1,ps2;
42        try
43        {
44             ps1=PrintSpoolerSingleton.getInstance();
45             ps1.manageJobs();    
46        }
47        catch(PrintSpoolerException e)
48        {
49                System.out.println(e.getMessage());
50        }
51             System.out.println("--------------------------");       
52        try
53        {
54            ps2=PrintSpoolerSingleton.getInstance(); 
55            ps2.manageJobs();      
56        }
57        catch(PrintSpoolerException e)
58        {
59            System.out.println(e.getMessage());
60        }
61     }
62 }

模式優缺點

優點
    • 提供了對唯一實例的受控訪問。因為單例類封裝了它的唯一實例,所以它可以嚴格控制客戶怎樣以及何時訪問它,並為設計及開發團隊提供了共享的概念。
    • 由於在系統內存中只存在一個對象,因此可以節約系統資源,對於一些需要頻繁創建和銷毀的對象,單例模式無疑可以提高系統的性能。
    • 允許可變數目的實例。我們可以基於單例模式進行擴展,使用與單例控制相似的方法來獲得指定個數的對象實例。
缺點
    • 由於單例模式中沒有抽象層,因此單例類的擴展有很大的困難。
    • 單例類的職責過重,在一定程度上違背了“單一職責原則”。因為單例類既充當了工廠角色,提供了工廠方法,同時又充當了產品角色,包含一些業務方法,將產品的創建和產品的本身的功能融合到一起。
    • 濫用單例將帶來一些負面問題,如為了節省資源將數據庫連接池對象設計為單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;現在很多面向對象語言(如Java、C#)的運行環境都提供了自動垃圾回收的技術,因此,如果實例化的對象長時間不被利用,系統會認為它是垃圾,會自動銷毀並回收資源,下次利用時又將重新實例化,這將導致對象狀態的丟失。

模式適用環境

在以下情況下可以使用單例模式:
    • 系統只需要一個實例對象,如系統要求提供一個唯一的序列號生成器,或者需要考慮資源消耗太大而只允許創建一個對象。
    • 客戶調用類的單個實例只允許使用一個公共訪問點,除了該公共訪問點,不能通過其他途徑訪問該實例。
    • 在一個系統中要求一個類只有一個實例時才應當使用單例模式。反過來,如果一個類可以有幾個實例共存,就需要對單例模式進行改進,使之成為多例模式。

模式應用

    (1)  java.lang.Runtime類
1 public class Runtime {
2     private static Runtime currentRuntime = new Runtime();
3     public static Runtime getRuntime() { 
4     return currentRuntime;
5     }
6     private Runtime() {}
7     ...
8 } 
    (2)  一個具有自動編號主鍵的表可以有多個用戶同時使用,但數據庫中只能有一個地方分配下一個主鍵編號,否則會出現主鍵重復,因此該主鍵編號生成器必須具備唯一性,可以通過單例模式來實現。  
    (3)  默認情況下,Spring會通過單例模式創建bean實例:
1 <bean id="date" class="java.util.Date" scope="singleton"/>

模式擴展

餓漢式單例類

懶漢式單例類

 
餓漢式單例與懶漢式單例類比較
    • 餓漢式單例類在自己被加載時就將自己實例化。單從資源利用效率角度來講,這個比懶漢式單例類稍差些。從速度和反應時間角度來講,則比懶漢式單例類稍好些。
    • 懶漢式單例類在實例化時,必須處理好在多個線程同時首次引用此類時的訪問限制問題,特別是當單例類作為資源控制器,在實例化時必然涉及資源初始化,而資源初始化很有可能耗費大量時間,這意味着出現多線程同時首次引用此類的機率變得較大,需要通過同步化機制進行控制。
 1     public static Object getInstance(){
 2         if(obj == null){
 3             synchronized(Object.class){
 4                 if(obj == null){
 5                     obj = new Object();
 6                 }
 7             }
 8         }
 9         return obj;
10     }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM