解析C#設(shè)計(jì)模式之單例模式
單例模式(Singleton),故名思議就是說(shuō)在整個(gè)應(yīng)用程序中,某一對(duì)象的實(shí)例只應(yīng)該存在一個(gè)。比如,一個(gè)類(lèi)加載數(shù)據(jù)庫(kù)中的數(shù)據(jù)到內(nèi)存中以提供只讀數(shù)據(jù),這就很適合使用單例模式,因?yàn)闆](méi)有必要在內(nèi)存中加載多份相同的數(shù)據(jù),另外,有些情況下不允許內(nèi)存中存在多分份相同的數(shù)據(jù),比如數(shù)據(jù)過(guò)大,內(nèi)存容不下兩份相同數(shù)據(jù)等等。
約定單例模式(Singleton by Convention)
這種方式有點(diǎn)“Too simple, Sometimes naïve”,他就是提示使用者,我是單例,不要重復(fù)初始化我,比如:
public class Database
{
/// <summary>
/// 警告,這是單例,不要初始化多次,否則,后果自負(fù).
/// </summary>
public Database() {}
};
一種情況是,根本不會(huì)注意到這個(gè)提示,其次是在很多時(shí)候,這些初始化是偷偷摸摸無(wú)意中發(fā)生的,比如通過(guò)反射,通過(guò)工廠產(chǎn)生(Activator.CreateInstance),通過(guò)注入等等,雖然有一個(gè)“約定大于配置”,但是這里不使用。
單例模式最常見(jiàn)的想法是提供一個(gè)全局的,靜態(tài)的對(duì)象。
public static class Globals
{
public static Database Database = new Database();
}
這種方式并沒(méi)有很安全。這個(gè)并沒(méi)有阻止用戶(hù)在其他地方new Database,并且,用戶(hù)可能并不知道有一個(gè)Globals類(lèi),里面有個(gè)Database單例。
經(jīng)典的實(shí)現(xiàn)方式
唯一的方式阻止用戶(hù)實(shí)例化對(duì)象是將構(gòu)造函數(shù)變成私有的,并且提供方法或者屬性返回唯一的內(nèi)部對(duì)象。
public class Database
{
private Database() { ... }
public static Database Instance { get; } = new Database();
}
現(xiàn)在將構(gòu)造函數(shù)設(shè)置為了私有,當(dāng)然設(shè)為私有依舊可以通過(guò)反射繼續(xù)調(diào)用,但是這畢竟需要額外操作,這已經(jīng)可以阻止大部分用戶(hù)直接實(shí)例化了。通過(guò)將實(shí)例定義為static靜態(tài), 使得其生命周期延長(zhǎng)至應(yīng)用程序運(yùn)行期間。
延遲初始化
以上方法線(xiàn)程安全,但是因?yàn)槭庆o態(tài)屬性,在類(lèi)的所有實(shí)例創(chuàng)建之前,或者任何靜態(tài)成員訪問(wèn)之前就會(huì)初始化,并且在每個(gè)AppDomain里都只會(huì)初始化一次。
如何實(shí)現(xiàn)延遲初始化,即將單例對(duì)象的構(gòu)造推遲到應(yīng)用程序首次請(qǐng)求該對(duì)象進(jìn)行時(shí),如果應(yīng)用程序永遠(yuǎn)不請(qǐng)求對(duì)象,則對(duì)象永遠(yuǎn)不會(huì)構(gòu)造。在之前,可以使用double check方式。其實(shí)要實(shí)現(xiàn)正確的double check還是有些問(wèn)題需要注意,比如上面這個(gè)例子,第一版可能這么寫(xiě),用一個(gè)鎖。
public class Database
{
private static Database db;
private static object olock = new object();
private Database()
{ }
public static Database GetInstance()
{
lock (olock)
{
if (db == null)
{
db = new Database();
}
return db;
}
}
}
這雖然線(xiàn)程安全,但是每次訪問(wèn)GetInstance,不論對(duì)象是否已經(jīng)創(chuàng)建,都需要獲取然后釋放鎖,比較消耗資源,所以,在外面再加一層判斷。
public class Database
{
private static Database db;
private static object olock = new object();
private Database()
{ }
public static Database GetInstance()
{
if (db == null)
{
lock (olock)
{
if (db == null)
{
db = new Database();
}
}
}
return db;
}
}
在訪問(wèn)對(duì)象之前判斷是否已經(jīng)初始化,如果初始化直接返回,這樣就避免了一次對(duì)鎖的訪問(wèn)。But,這里仍然存在問(wèn)題。假設(shè)Database初始化起來(lái)耗時(shí),當(dāng)線(xiàn)程A獲得鎖正在對(duì)db進(jìn)行初始化的時(shí)候,線(xiàn)程B在最外層判斷db是否為空,這個(gè)時(shí)候,線(xiàn)程A正在初始化db,有可能只初始化了部分,這個(gè)時(shí)候db就可能不為空,直接返回了沒(méi)有完全初始化完全的對(duì)象,這可能導(dǎo)致線(xiàn)程B崩潰。
解決方式是,將對(duì)象存儲(chǔ)到臨時(shí)變量中,然后以原子寫(xiě)的方式存儲(chǔ)到db中,如下
public class Database
{
private static Database db;
private static object olock = new object();
private Database()
{ }
public static Database GetInstance()
{
if (db == null)
{
lock (olock)
{
if (db == null)
{
var temp = new Database();
Volatile.Write(ref db, temp);
}
}
}
return db;
}
}
非常繁瑣,雖然實(shí)現(xiàn)了延遲初始化,但是跟開(kāi)頭的靜態(tài)字段對(duì)比,復(fù)雜太多,而且一不小心就會(huì)寫(xiě)錯(cuò)。雖然可以簡(jiǎn)化為:
public static Database GetInstance()
{
if (db == null)
{
var temp = new Database();
Interlocked.CompareExchange(ref db, temp, null);
}
return db;
}
該方法看起來(lái)沒(méi)有用鎖,temp對(duì)象有可能會(huì)被初始化兩次,但是在將temp寫(xiě)入到db的時(shí)候,Interlock.CompareExchange會(huì)保證只會(huì)有1個(gè)對(duì)象正確被寫(xiě)入到db,沒(méi)有被寫(xiě)入的temp對(duì)象會(huì)被垃圾回收,這種方式速度比上述的double check要快。但仍然需要學(xué)習(xí)成本。幸好,C#提供了Lazy方法:
private static Lazy<Database> db = new Lazy<Database>(() => new Database(), true); public static Database GetInstance() => db.Value;
簡(jiǎn)單且完美。
依賴(lài)注入與單例模式
前面的單例模式其實(shí)是一種代碼侵入的做法,就是要想一個(gè)原本沒(méi)有實(shí)現(xiàn)單例的代碼要實(shí)現(xiàn)單例,需要修改代碼實(shí)現(xiàn),并且這個(gè)代碼還容易出錯(cuò)。有些人認(rèn)為單例模式的唯一正確的做法就是在IOC依賴(lài)注入中,這樣不需要修改源代碼,通過(guò)依賴(lài)注入框架來(lái)實(shí)現(xiàn)依賴(lài)注入,在統(tǒng)一的入口,統(tǒng)一的管理生命周期,在ASP.NET Core MVC中,在Startup的ConfigureServices代碼中:
services.AddSingleton<Database>();
或者加入需要用IDatabase的地方,要用Database單例的話(huà):
services.AddSingleton<IDatabase,Database>();
在ASP.NET Core MVC的后續(xù)代碼中,只要用到IDatabase的地方,就會(huì)用Database的單例來(lái)實(shí)現(xiàn),不需要我們?cè)贒atabase內(nèi)做任何修改。在使用的時(shí)候,只需要引用IServiceProvider接口里的GetService方法,IServiceProvider是由ASP.NET Core MVC的IOC框架直接提供,不需要特別處理:
public XXXController(IServiceProvider serviceProvider)
{
var db = serviceProvider.GetService<IDatabase>();
}
單態(tài)模式(Monostate)
單態(tài)模式是單例模式的一個(gè)變種,它是一個(gè)普通的類(lèi),但是其行為和表現(xiàn)就像單例模式。
比如我們?cè)趯?duì)一個(gè)公司的人員結(jié)構(gòu)進(jìn)行建模,一個(gè)典型的公司一般只會(huì)有一個(gè)CEO,
public class ChiefExecutiveOfficer
{
private static string name;
private static int age;
public string Name
{
get => name;
set => name = value;
}
public int Age
{
get => age;
set => age = value;
}
}
這個(gè)類(lèi)的屬性有g(shù)et,set,但是其背后的私有字段都是靜態(tài)的。所以不管這個(gè)ChiefExecutiveOfficer實(shí)例化多少次,其內(nèi)部引用的都是同一個(gè)數(shù)據(jù)。比如,可以實(shí)例化兩個(gè)對(duì)象,但是其內(nèi)部的內(nèi)容是一模一樣的。
單態(tài)模式簡(jiǎn)單,但是容易引起混亂,所以要想簡(jiǎn)單,要想實(shí)現(xiàn)單例效果,最好還是使用IOC這種依賴(lài)注入的框架,讓它來(lái)幫助我們來(lái)管理實(shí)例及其生命周期。
以上就是解析C#設(shè)計(jì)模式之單例模式的詳細(xì)內(nèi)容,更多關(guān)于c# 單例模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- c# 單例模式的實(shí)現(xiàn)
- C#實(shí)現(xiàn)單例模式的幾種方法總結(jié)
- c#中單例類(lèi)與靜態(tài)類(lèi)的區(qū)別以及使用場(chǎng)景
- 關(guān)于c#中單例模式的一些問(wèn)題
- c# 單例模式的實(shí)現(xiàn)方法
- c#設(shè)計(jì)模式之單例模式的實(shí)現(xiàn)方式
- 淺談C#單例模式的實(shí)現(xiàn)和性能對(duì)比
- C#單例模式(Singleton Pattern)詳解
- c#單例模式(Singleton)的6種實(shí)現(xiàn)
- 詳解C# Socket簡(jiǎn)單例子(服務(wù)器與客戶(hù)端通信)
- 淺談C#多線(xiàn)程簡(jiǎn)單例子講解
- C# 創(chuàng)建單例的多種方式
相關(guān)文章
C#8.0中新語(yǔ)法“is{}“的介紹及使用小結(jié)
is模式匹配操作符通過(guò)測(cè)試一個(gè)變量是否是一個(gè)對(duì)象,來(lái)判斷其是否不為null值,本文主要介紹了C#8.0中新語(yǔ)法“is{}“的介紹及使用小結(jié),感興趣的可以了解一下2023-11-11
C#任務(wù)并行Parellel.For和Parallel.ForEach
這篇文章介紹了C#任務(wù)并行Parellel.For和Parallel.ForEach的用法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07
詳解C#打開(kāi)和關(guān)閉可執(zhí)行文件
這篇文章主要介紹了C#打開(kāi)和關(guān)閉可執(zhí)行文件,以QQ應(yīng)用程序?yàn)槔?,需要的朋友可以參考?/div> 2015-12-12
C#獲取文件名和文件路徑的兩種實(shí)現(xiàn)方式
這篇文章主要介紹了C#獲取文件名和文件路徑的兩種實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
C#創(chuàng)建Windows服務(wù)的實(shí)現(xiàn)方法
這篇文章主要介紹了C#創(chuàng)建Windows服務(wù)的實(shí)現(xiàn)方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-03-03
基于Aforge攝像頭調(diào)用簡(jiǎn)單實(shí)例
這篇文章主要為大家詳細(xì)介紹了基于Aforge攝像頭調(diào)用的簡(jiǎn)單實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10
C#實(shí)現(xiàn)合并多個(gè)word文檔的方法
這篇文章主要介紹了C#實(shí)現(xiàn)合并多個(gè)word文檔的方法,是C#針對(duì)Word文檔操作的一個(gè)非常重要的技巧,需要的朋友可以參考下2014-09-09最新評(píng)論

