今天我們來講一下單例模式,下麵我們來用winform來做一個簡單的展示,就是點擊一個菜單,彈出另一個窗體(做成父子窗體的形式)。 建一個窗體(父窗體),拖一個MenuStrip,再建一個窗體(子窗體)。 然後: 現在,我們看一下執行結果。 我們可以看到,每次我們點擊一下工具,都會彈出一個新的窗體,我 ...
今天我們來講一下單例模式,下麵我們來用winform來做一個簡單的展示,就是點擊一個菜單,彈出另一個窗體(做成父子窗體的形式)。
建一個窗體(父窗體),拖一個MenuStrip,再建一個窗體(子窗體)。
然後:
1 private void Form1_Load(object sender, System.EventArgs e) 2 { 3 this.IsMdiContainer = true; 4 } 5 6 private void 工具欄ToolStripMenuItem_Click(object sender, System.EventArgs e) 7 { 8 Form2 form2 = new Form2(); 9 form2.MdiParent = this; 10 form2.Show(); 11 }
現在,我們看一下執行結果。
我們可以看到,每次我們點擊一下工具,都會彈出一個新的窗體,我們想要的結果是:之彈出一次這個窗體就行。
有些伙伴說,可以用ShowDialog() 啊,在此,說明一下,在父子窗體中,子窗體是不能用ShowDialog()出來的,再退一步講,即便是能用ShowDialog(),但是這是一個阻塞機制,很不靈活。
那麼,為了實現我們想要的結果,我們該如何做呢?
簡單啊,我們只需要修改一下點擊事件里的代碼就可以了。
先聲明一個子窗體的全局變數,然後修改一下代碼:
1 private Form2 form2; 2 private void Form1_Load(object sender, System.EventArgs e) 3 { 4 this.IsMdiContainer = true; 5 } 6 7 private void 工具欄ToolStripMenuItem_Click(object sender, System.EventArgs e) 8 { 9 if (form2 == null) 10 { 11 form2 = new Form2(); 12 form2.MdiParent = this; 13 form2.Show(); 14 } 15 }
這樣就可以實現我們想要的結果了,這樣就完了嘛?
這裡存在兩個問題:
1、如果我有很多按鈕,每個按鈕都想彈出這個子窗體,想實現這個結果,需要複製粘貼這些代碼,顯然是很失敗的做法。
2、上述結果,如果我關閉了打開的窗體,我在點擊工具菜單,則不會再彈出窗體來了。(因為關閉窗體後,該窗體僅僅是Disposed了,但對象還不是null,所以判斷是否為null是有一定的問題的。)
針對上述兩個問題,我們來改進一下。就用到今天要講的單例模式了,好,我們來看一下如何實現。
Form2中的代碼
1 public partial class Form2 : Form 2 { 3 //聲明一個靜態的類變數 4 private static Form2 form2 = null; 5 //構造方法私有,外部代碼不能直接new來實例化它 6 private Form2() 7 { 8 InitializeComponent(); 9 } 10 //得到類實例的方法,返回值就是本類對象,註意也是靜態的。 11 public static Form2 GetForm() 12 { 13 //當內部的form2是null或者被Dispose過,則new它 14 //並且設計其MdiParent為Form1,此時將實例化的對象存在靜態的變數form2中,以後就可以不用實例化而得到它了 15 if (form2 == null || form2.IsDisposed) 16 { 17 form2 = new Form2(); 18 form2.MdiParent = Form1.ActiveForm; 19 } 20 return form2; 21 } 22 }
Form1 中的調用
1 public partial class Form1 : Form 2 { 3 4 public Form1() 5 { 6 InitializeComponent(); 7 } 8 9 private void Form1_Load(object sender, System.EventArgs e) 10 { 11 this.IsMdiContainer = true; 12 } 13 private void 工具欄ToolStripMenuItem_Click(object sender, System.EventArgs e) 14 { 15 Form2.GetForm().Show(); 16 } 17 }
這樣,就達到了我們想要的效果了。
我們來總結一下:
單例模式:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
通常我們剋讓一個全局變數是的一個對象被訪問,但它不能防止你實例化多個對象。最好的辦法就是,讓類自身負責保存它的唯一實例,這個類可以保證沒有其他實例可以被創建,並且他可以提供一個訪問該實例的方法。
好,我們來寫一下單例模式的代碼
1 class Singleton 2 { 3 private static Singleton instance; 4 //構造方法讓其private,這就堵死了外界利用用new創建次類實例的可能 5 private Singleton() 6 { 7 8 } 9 //此方法是獲得本類實例的唯一全局訪問點 10 public static Singleton GetInstance() 11 { 12 //若實例不存在,則new一個新實例,否則返回已有的實例 13 if (instance==null) 14 { 15 instance = new Singleton(); 16 } 17 return instance; 18 } 19 }
客戶端:
1 public static void Main() 2 { 3 Singleton s1 = Singleton.GetInstance(); 4 Singleton s2 = Singleton.GetInstance(); 5 //比較兩次實例化後對象的結果是否相同 6 if (s1==s2) 7 { 8 Console.WriteLine("兩個對象是相同的實例。"); 9 } 10 Console.ReadKey(); 11 }
另外還有一個問題,如果多線程中,多個線程同時訪問Singleton類,調用GetInstance()方法,會有可能創造多個實例的。所以,我們可以用lock進行處理一下。
好,我們來看用lock處理後的Singleton類
1 class Singleton 2 { 3 private static Singleton instance; 4 //程式運行時創建一個靜態只讀的進程輔助對象 5 private static readonly object syncRoot = new object(); 6 //構造方法讓其private,這就堵死了外界利用用new創建次類實例的可能 7 private Singleton() 8 { 9 10 } 11 //此方法是獲得本類實例的唯一全局訪問點 12 public static Singleton GetInstance() 13 { 14 //在同一個時刻加了鎖的那部分程式只有一個線程可以進入 15 lock (syncRoot) 16 { 17 //若實例不存在,則new一個新實例,否則返回已有的實例 18 if (instance == null) 19 { 20 instance = new Singleton(); 21 } 22 } 23 return instance; 24 } 25 }
對於上述的代碼,小伙伴們發現了一個問題沒有,就是不管instance是不是為null,都會先加鎖,這勢必會影響性能的,好我們來看一下優化後的:
1 class Singleton 2 { 3 private static Singleton instance; 4 //程式運行時創建一個靜態只讀的進程輔助對象 5 private static readonly object syncRoot = new object(); 6 //構造方法讓其private,這就堵死了外界利用用new創建次類實例的可能 7 private Singleton() 8 { 9 10 } 11 //此方法是獲得本類實例的唯一全局訪問點 12 public static Singleton GetInstance() 13 { 14 //先判斷實例是否存在,如果不存在再加鎖處理 15 if (instance==null) 16 { 17 //在同一個時刻加了鎖的那部分程式只有一個線程可以進入 18 lock (syncRoot) 19 { 20 //若實例不存在,則new一個新實例,否則返回已有的實例 21 if (instance == null) 22 { 23 instance = new Singleton(); 24 } 25 } 26 } 27 return instance; 28 } 29 }
其實再實際應用當中,C#與用功語言運行庫也提供了一種“靜態初始化”方法,這種方法不需要開發人員顯示的編寫線程安全代碼,即可解決多線程環境下他是不安全的問題。
好,下麵我們來看一下“靜態初始化”方法的單例模式
1 //sealed阻止發生派生,而派生可能會增加實例 2 public sealed class Singleton 3 { 4 //在第一次引用類的任何成員時創建實例,共功與原運行庫負責處理變數初始化。 5 private static readonly Singleton instance = new Singleton(); 6 private Singleton() 7 { 8 } 9 10 public static Singleton GetInstance() 11 { 12 return instance; 13 } 14 }
這種靜態初始化的方式是自己被載入時就將自己實例化,所以被形象的成為惡漢式單例類,原先的單例模式處理是要再第一次被引用時,才會將自己實例化,所以被稱為懶漢單例類。
好,單例模式我們就介紹完了,下一篇博文我們講 橋接模式
本系列將持續更新,喜歡的小伙伴可以點一下關註和推薦,謝謝大家的支持