1.定義
保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
2.適用性
1)當類只能有一個實例而客戶可以從一個眾所周知的訪問點訪問它時。
2)當這個唯一實例應該是通過子類化可擴展的,並且客戶應該無需更改代碼就能使用一個擴展的實例時。
3.結構
圖1 單例模式類圖
注:
Singleton:定義一個Instance操作,允許客戶訪問它的唯一實例。Instance是一個類操作(C#中為靜態方法),Singleton負責創建它自己的唯一實例。
4.實現方法
1)經典模式
靜態變量uniqueInstance存儲唯一實例。公有靜態方法GetInstance提供訪問SingletonFirst的全局訪問點,由於除了GetInstance方法外,類的成員均為私有的,所以GetInstance提供的為唯一訪問方式。在GetInstance()方法中,對uniqueInstance是否為null做了判斷,因此使得對象只能被實例化一次。
1 public class SingletonFirst 2 { 3 private static SingletonFirst uniqueInstance; 4 private SingletonFirst() { } 5 6 public static SingletonFirst GetInstance() 7 { 8 if(uniqueInstance==null) 9 { 10 uniqueInstance = new SingletonFirst(); 11 } 12 return uniqueInstance; 13 } 14 }
2)使用屬性而不是方法
這種實現方式與第一種方式的區別在於客戶通過屬性獲得對象實例。
1 public class SingletonSecond 2 { 3 private static SingletonSecond uniqueInstance; 4 private SingletonSecond() { } 5 public static SingletonSecond Instance 6 { 7 get 8 { 9 if (uniqueInstance == null) 10 { 11 uniqueInstance = new SingletonSecond(); 12 } 13 return uniqueInstance; 14 } 15 } 16 }
3)只能獲得一次單例
這種單例模式的實現方式較之前幾種有較大的差別,客戶不能反復調用Instance屬性獲得實例;客戶要將第一次獲得的實例賦給一個變量,之后若要訪問實例,則只能通過使用這一變量的間接方式。
1 public class SingletonThird 2 { 3 private static bool instanceFlag = false; 4 private SingletonThird() { } 5 6 public static SingletonThird Instance 7 { 8 get 9 { 10 if (!instanceFlag) 11 { 12 return new SingletonThird(); 13 } 14 else 15 { 16 return null; 17 } 18 } 19 } 20 }
4)支持並發訪問
這種單例模式的實現方式與第一種基本一致,只不過在聲明uniqueInstanc的同時完成對象的實例化。優點是靜態字段在類首次被使用前初始化,可以防止並發訪問。
1 public class SingletonFourth 2 { 3 private static readonly SingletonFourth uniqueInstance = new SingletonFourth(); 4 private SingletonFourth() { } 5 6 public static SingletonFourth GetInstance() 7 { 8 return uniqueInstance; 9 } 10 }
5)使用私有靜態構造器
這種方式使用了私有的靜態構造器。靜態構造器在類第一次被訪問前由CLR自動調用,達到防止並發訪問的目的。使用靜態構造器而不是靜態初始化器的好處是:可以在靜態構造器中處理異常。
1 public class SingletonFifth 2 { 3 private static readonly SingletonFifth uniqueInstance; 4 static SingletonFifth() 5 { 6 uniqueInstance = new SingletonFifth(); 7 } 8 9 public static SingletonFifth GetInstance() 10 { 11 return uniqueInstance; 12 } 13 }
6)使用鎖
這種方式采用加鎖的辦法來防止並發訪問。缺點就是鎖的開銷比較大,如果用戶對於性能比較關心,那么不建議采用這種方式。也可以考慮其他開銷比較小的同步機制。
1 public class SingletonSixth 2 { 3 private static SingletonSixth uniqueInstance; 4 private static Object lockObj = new object(); 5 private SingletonSixth() { } 6 7 public static SingletonSixth GetInstance() 8 { 9 lock (lockObj) 10 { 11 if (uniqueInstance == null) 12 { 13 uniqueInstance = new SingletonSixth(); 14 } 15 return uniqueInstance; 16 } 17 } 18 }
7)延遲初始化
使用Lazy<T>達到延遲初始化的目的,但這種方式是非線程安全的。
1 public class SingletonSeventh 2 { 3 private static readonly Lazy<SingletonSeventh> lazy = 4 new Lazy<SingletonSeventh>(() => new SingletonSeventh()); 5 private SingletonSeventh(){} 6 7 public static SingletonSeventh Instance 8 { 9 get 10 { 11 return lazy.Value; 12 } 13 } 14 }
8)最簡潔的方式
這種方法與標准的單例模式結構不符,但這種方式的確實現了單例模式, 符合單例模式的定義:只有一個實例且提供一個全局訪問點uniqueInstance靜態變量在聲明時初始化,同時構造器可訪問性為private,確保類不允許在外部實例化uniqueInstance變量的可訪問性為public,所以全局的訪問點就是uniqueInstance。
1 public class SingletonEighth 2 { 3 public static readonly SingletonEighth uniqueInstance = new SingletonEighth(); 4 private SingletonEighth() 5 { 6 //這里執行初始化工作或其他任務 7 } 8 }
5.擴展單例模式:使類可以有幾個實例
1 public class SingletonExpansion 2 { 3 private static int count = 0; 4 private static SingletonExpansion uniqueInstance; 5 private SingletonExpansion(){} 6 7 public static SingletonExpansion Instance 8 { 9 get 10 { 11 if (count < 2) 12 { 13 uniqueInstance = new SingletonExpansion(); 14 count++; 15 return uniqueInstance; 16 } 17 else 18 { 19 return uniqueInstance; 20 } 21 } 22 } 23 }
6 概念辨析
1)使用靜態成員與使用單例模式之間的比較:
相同點:
- 使用靜態成員與使用單例模式都可以獲得類的唯一實例。
- 使用靜態成員與使用單例模式都可以將數據一次性地加載到內存中,已提高系統運行效率。
不同點:
- 使用單例模式而不使用靜態成員可能會增加代碼的復雜度。
- 單例模式提供了更多的控制機制。可以在構造器中處理數據或執行其他操作;可以控制類的狀態,根據類的狀態完成適當的操作。
- 在單例模式中運用接口,使得用戶可以擴展程序而不比修改代碼,這也符合開放封閉原則(OCP)。
2)一個類只有唯一的實例不一定就運用了單例模式
單例模式的兩個要素:
- 有且僅有一個實例。
- 必須提供一個全局的訪問點。
上述兩個條件缺一不可,否則就不是單例模式。
7 實現方式總結
1)實現了延遲初始化的為:SingletonFirst,SingletonSecond,SingletonThird,SingletonSeventh。
延遲初始化的好處:若對象占用資源(時間上創建時間比較長或空間上耗費內存較大)比較大,或程序的執行過程中從未使用到,那么延遲初始化可以避免耗費資源。如果對象並不占用較多的資源那么采用哪種方式都無所謂了。
2)線程安全的:SingletonFourth,SingletonFifth,SingletonSixth。
多線程編程中其他幾種實現方法可能會有多個實例。
8 .NET Framework 中的單例模式
1) Microsoft.SqlServer.Server.SmiContextFactory
此方法實現方式與SingletonEighth的實現方式相同
源碼概要
1 namespace Microsoft.SqlServer.Server { 2 3 using System; 4 5 using System.Data.Common; 6 7 using System.Data.SqlClient; 8 9 using System.Diagnostics; 10 11 sealed internal class SmiContextFactory 12 { 13 14 public static readonly SmiContextFactory Instance = new SmiContextFactory(); 15 //省略一些變量 16 private SmiContextFactory() { 17 18 //省略了具體實現} 19 20 //省略其他方法 21 22 } 23 }
2) sealed internal class SqlConnectionFactory
此方法實現方式與SingletonEighth的實現方式相同
源碼概要
1 namespace System.Data.SqlClient 2 { 3 4 using System; 5 6 using System.Data.Common; 7 8 //省略其他using 9 10 using Microsoft.SqlServer.Server; 11 sealed internal class SqlConnectionFactory : DbConnectionFactory { 12 private SqlConnectionFactory() : 13 base(SqlPerformanceCounters.SingletonInstance) {} 14 15 public static readonly SqlConnectionFactory SingletonInstance = new SqlConnectionFactory(); 16 //省略以下其他代碼 17 }
3) sealed internal class SqlPerformanceCounters
此方法實現方式與SingletonEighth的實現方式相同
源碼概要
1 namespace System.Data.SqlClient 2 { 3 4 using System; 5 6 using System.Data.Common; 7 8 //省略其他using語句 9 10 using Microsoft.SqlServer.Server; 11 12 sealed internal class SqlPerformanceCounters : 13 DbConnectionPoolCounters { 14 15 private const string CategoryName = ".NET Data Provider for SqlServer"; 16 17 private const string CategoryHelp = "Counters for System.Data.SqlClient"; 18 19 20 public static readonly SqlPerformanceCounters SingletonInstance = new SqlPerformanceCounters(); 21 22 [System.Diagnostics.PerformanceCounterPermissionAttribute 23 (System.Security.Permissions.SecurityAction.Assert, PermissionAccess=PerformanceCounterPermissionAccess.Write, MachineName=".", CategoryName=CategoryName)] 24 25 private SqlPerformanceCounters() : base (CategoryName, CategoryHelp) { 26 } 27 28 } 29 }