第 21 章 —— 单例模式
(復制粘貼是最容易的編程,但也是最沒有價值的編程。復制粘貼就會造成重復,當需求變化或有 Bug 時就需要改多個地方)
把聲明的工作放到類的全局變量中完成(private FormToolbox ftb;),這樣就可以在方法中判斷這個變量是否實例化過了。
21.3 是否實例化是自己的責任
單例類是否實例化過,應該由單例類自己判斷。(而不是將判斷的代碼放到調用類當中)
(如果已經實例化過,則不需要再實例化)實例化其實就是 new 的過程,而如果不對構造方法做改動的話,是不可能阻止他人不去用 new 的,所以我們完全可以直接把這個類的構造方法改成私有(private)。
于是,只要將“工具箱”類的構造方法寫成 private 的,那么外部程序就不能用 new 來實例化它了。
?
對于外部代碼,不能用 new 來實例化它,但是我們完全可以再寫一個 public 方法,叫做 GetInstance(),這個方法的目的就是返回一個類實例,而此方法中,去做是否有實例化的判斷。
如果沒有實例化過,由調用 private 的構造方法 new 出這個實例,之所以它可以調用是因為它們在同一個類中,private 方法可以被調用。
參考代碼:
public class FormToolbox : Form{private static FormToolbox ftb = null; //聲明一個靜態的類變量(全局可用)private FormToolbox() //構造方法私有,外部代碼不能直接用new來實例化它 {//InitializeComponent(); //這個方法也許只有窗體才有,其他的類只要空著就行 }public static FormToolbox GetInstance() //得到類實例的方法,返回值就是本類對象,注意也是靜態的 {if(ftb == null || ftb.IsDisposed){ftb = new FormToolbox(); //當內部的ftb是null或者被Disposed過,則new它。此時將實例化的對象存在靜態的變量ftb中,以后就可以不用實例化而得到它//可以在這里繼續設置ftb的其他屬性 }return ftb;}}(當窗體關閉后,實例并沒有變為 null,而只是 Disposed —— if (ftb == null || ftb.IsDisposed))
這樣一來,客戶端不再考慮是否需要去實例化的問題,而把責任都給了應該負責的類去處理。這就是單例模式。
21.4 單例模式
單例模式,保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。(因為是全局訪問點,所以該方法必須是靜態的)
通常,我們可以使用一個全局變量(static)使得一個對象被訪問,但它不能防止你實例化多個對象(對象的屬性也可能會發生改變)。一個最好的辦法就是,讓類自身負責保存它的唯一實例。
這個類可以保證沒有其他實例可以被創建,并且它可以提供一個訪問該實例的方法(即返回該實例的靜態方法)。
單例模式的好處:
因為單例模式封裝了它的唯一實例,所以它可以嚴格地控制客戶怎樣訪問它以及何時訪問它。簡單地說就是對唯一實例的受控訪問。
單例與實用類(如Math類)的靜態方法類似,實用類通常也會采用私有化的構造方法來避免其有實例。
但它們也有很多的不同,比如:
實用類不保存狀態,僅提供一些靜態方法或靜態屬性讓你使用,而單例類是有狀態的。
實用類不能用于繼承多態,而單例雖然實例唯一,卻是可以有子類來繼承。
實用類只不過是一些方法、屬性的集合,而單例卻是有著唯一對象的實例。
★21.5 多線程時的單例
另外,還需要注意一些細節,比如說,多線程的程序中,當多個線程同時(注意是同時)訪問單例類,調用GetInstance()方法,是會有可能造成創建多個實例的。
解決的方法是給進程加鎖來處理:
lock 語句的含義—— lock 可以確保當一個線程位于代碼的臨界區時,另一個線程不進入臨界區。(如果其他線程視圖進入鎖定的代碼,則它將一直等待(即被阻止),直到該對象被釋放)
(當寫在了 .NET 網站程序的啟動部分時應該就不用考慮這個多線程的問題了,因為它的實例化并不是由線程發起的)
public class Singleton{private static Singleton instance;private static readonly object syncRoot = new object(); //程序運行時創建一個靜態只讀的進程輔助對象private Singleton(){}public static Singleton GetInstance(){lock(syncRoot) //在同一個時刻,加了鎖的那部分程序只有一個線程可以進入 {if(instance==null){instance = new Singleton();}}return instance;}}由于有了 lock,就保證了多線程環境下的同時訪問也不會造成多個實例的生成。(我們并不知道 instance 實例有沒有被創建過,所以無法對它加鎖,于是創建了一個 syncRoot 并選擇對其加鎖)
但是,當前這種寫法,每次調用 GetInstance 方法時都需要加鎖,這種做法是會影響性能的,所以可以對這個類做如下改良:
21.6 雙重鎖定
public class Singleton{private static Singleton instance;private static readonly object syncRoot = new object(); //程序運行時創建一個靜態只讀的進程輔助對象private Singleton(){}public static Singleton GetInstance(){if(instance==null) //先判斷實例是否存在,不存在時再加鎖處理 {lock (syncRoot) //在同一個時刻,加了鎖的那部分程序只有一個線程可以進入 {if (instance == null){instance = new Singleton();}}}return instance;}}現在這樣就不用每次都讓線程加鎖了,而只是在實例未被創建的時候再加鎖處理。同時也能保證多線程的安全。這種做法被稱為 Double-Check Locking(雙重鎖定)
這里為什么要兩次判斷 instance 是否為空呢?
當 instance 為 null 并且同時有兩個線程調用 GetInstance()方法時,它們將都可以通過第一重 instance == null 的判斷。然后由于 lock 機制,這兩個線程只有一個進入,另一個在外排隊等候,必須要其中的一個進入并出來后,另一個才能進入。
而此時如果沒有了第二重的 instance 是否為 null 的判斷,則第一個線程創建了實例,而第二個線程還是可以繼續再創建新的實例,這就沒有達到單例的目的。
21.7 靜態初始化
靜態初始化的實現方式更為簡單,同時也有著與單例模式類似的特性,但兩者仍有一些不同,比如靜態初始化只能在靜態初始化期間或在類構造函數中分配變量(instance 變量標記為 readonly)
附加:單例模式的繼承
父類:
public class Singleton<T> where T : class, new() {private static T _instance; //使用了泛型T,而子類中又用自身進行泛型,所以這里可以直接將 _instance 理解為子類實例(也是子類唯一實例)private static readonly object syslock = new object();public static T getInstance(){ //線程安全鎖if (_instance == null){lock (syslock){if (_instance == null){_instance = new T();}}}return _instance;} }子類需要泛型一下:
public class Two : Singleton<Two> {public void Show(){Console.WriteLine("Two Sub class.......");} }調用:
Two.getInstance().Show();?
轉載于:https://www.cnblogs.com/zhangchaoran/p/8465491.html
總結
以上是生活随笔為你收集整理的第 21 章 —— 单例模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: itchat 动态注册
- 下一篇: Struts2 (三)OGLN